From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- security/manager/ssl/tests/gtest/CertDBTest.cpp | 60 +++ .../manager/ssl/tests/gtest/DataStorageTest.cpp | 225 ++++++++ .../ssl/tests/gtest/DeserializeCertTest.cpp | 96 ++++ security/manager/ssl/tests/gtest/MD4Test.cpp | 76 +++ security/manager/ssl/tests/gtest/OCSPCacheTest.cpp | 337 ++++++++++++ security/manager/ssl/tests/gtest/README.txt | 2 + security/manager/ssl/tests/gtest/STSParserTest.cpp | 144 ++++++ .../manager/ssl/tests/gtest/TLSIntoleranceTest.cpp | 568 +++++++++++++++++++++ security/manager/ssl/tests/gtest/moz.build | 29 ++ 9 files changed, 1537 insertions(+) create mode 100644 security/manager/ssl/tests/gtest/CertDBTest.cpp create mode 100644 security/manager/ssl/tests/gtest/DataStorageTest.cpp create mode 100644 security/manager/ssl/tests/gtest/DeserializeCertTest.cpp create mode 100644 security/manager/ssl/tests/gtest/MD4Test.cpp create mode 100644 security/manager/ssl/tests/gtest/OCSPCacheTest.cpp create mode 100644 security/manager/ssl/tests/gtest/README.txt create mode 100644 security/manager/ssl/tests/gtest/STSParserTest.cpp create mode 100644 security/manager/ssl/tests/gtest/TLSIntoleranceTest.cpp create mode 100644 security/manager/ssl/tests/gtest/moz.build (limited to 'security/manager/ssl/tests/gtest') diff --git a/security/manager/ssl/tests/gtest/CertDBTest.cpp b/security/manager/ssl/tests/gtest/CertDBTest.cpp new file mode 100644 index 000000000..e6b773cd6 --- /dev/null +++ b/security/manager/ssl/tests/gtest/CertDBTest.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "nsCOMPtr.h" +#include "nsIPrefService.h" +#include "nsISimpleEnumerator.h" +#include "nsIX509Cert.h" +#include "nsIX509CertDB.h" +#include "nsIX509CertList.h" +#include "nsServiceManagerUtils.h" + +TEST(psm_CertDB, Test) +{ + { + nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + ASSERT_TRUE(prefs) << "couldn't get nsIPrefBranch"; + + // When PSM initializes, it attempts to get some localized strings. + // As a result, Android flips out if this isn't set. + nsresult rv = prefs->SetBoolPref("intl.locale.matchOS", true); + ASSERT_TRUE(NS_SUCCEEDED(rv)) << "couldn't set pref 'intl.locale.matchOS'"; + + nsCOMPtr certdb(do_GetService(NS_X509CERTDB_CONTRACTID)); + ASSERT_TRUE(certdb) << "couldn't get certdb"; + + nsCOMPtr certList; + rv = certdb->GetCerts(getter_AddRefs(certList)); + ASSERT_TRUE(NS_SUCCEEDED(rv)) << "couldn't get list of certificates"; + + nsCOMPtr enumerator; + rv = certList->GetEnumerator(getter_AddRefs(enumerator)); + ASSERT_TRUE(NS_SUCCEEDED(rv)) << "couldn't enumerate certificate list"; + + bool foundBuiltIn = false; + bool hasMore = false; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr supports; + ASSERT_TRUE(NS_SUCCEEDED(enumerator->GetNext(getter_AddRefs(supports)))) + << "couldn't get next certificate"; + + nsCOMPtr cert(do_QueryInterface(supports)); + ASSERT_TRUE(cert) << "couldn't QI to nsIX509Cert"; + + ASSERT_TRUE(NS_SUCCEEDED(cert->GetIsBuiltInRoot(&foundBuiltIn))) << + "GetIsBuiltInRoot failed"; + + if (foundBuiltIn) { + break; + } + } + + ASSERT_TRUE(foundBuiltIn) << "didn't load any built-in certificates"; + + printf("successfully loaded at least one built-in certificate\n"); + + } // this scopes the nsCOMPtrs +} diff --git a/security/manager/ssl/tests/gtest/DataStorageTest.cpp b/security/manager/ssl/tests/gtest/DataStorageTest.cpp new file mode 100644 index 000000000..eaab50878 --- /dev/null +++ b/security/manager/ssl/tests/gtest/DataStorageTest.cpp @@ -0,0 +1,225 @@ +/* -*- 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 "gtest/gtest.h" + +#include "mozilla/DataStorage.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsNetUtil.h" +#include "nsPrintfCString.h" +#include "nsStreamUtils.h" +#include "prtime.h" + +using namespace mozilla; + +class psm_DataStorageTest : public ::testing::Test +{ +protected: + virtual void SetUp() + { + const ::testing::TestInfo* const testInfo = + ::testing::UnitTest::GetInstance()->current_test_info(); + NS_ConvertUTF8toUTF16 testName(testInfo->name()); + storage = DataStorage::Get(testName); + storage->Init(dataWillPersist); + } + + RefPtr storage; + bool dataWillPersist; +}; + +NS_NAMED_LITERAL_CSTRING(testKey, "test"); +NS_NAMED_LITERAL_CSTRING(testValue, "value"); +NS_NAMED_LITERAL_CSTRING(privateTestValue, "private"); + +TEST_F(psm_DataStorageTest, GetPutRemove) +{ + EXPECT_TRUE(dataWillPersist); + + // Test Put/Get on Persistent data + EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent)); + // Don't re-use testKey / testValue here, to make sure that this works as + // expected with objects that have the same semantic value but are not + // literally the same object. + nsCString result = storage->Get(NS_LITERAL_CSTRING("test"), + DataStorage_Persistent); + EXPECT_STREQ("value", result.get()); + + // Get on Temporary/Private data with the same key should give nothing + result = storage->Get(testKey, DataStorage_Temporary); + EXPECT_TRUE(result.IsEmpty()); + result = storage->Get(testKey, DataStorage_Private); + EXPECT_TRUE(result.IsEmpty()); + + // Put with Temporary/Private data shouldn't affect Persistent data + NS_NAMED_LITERAL_CSTRING(temporaryTestValue, "temporary"); + EXPECT_EQ(NS_OK, storage->Put(testKey, temporaryTestValue, + DataStorage_Temporary)); + EXPECT_EQ(NS_OK, storage->Put(testKey, privateTestValue, + DataStorage_Private)); + result = storage->Get(testKey, DataStorage_Temporary); + EXPECT_STREQ("temporary", result.get()); + result = storage->Get(testKey, DataStorage_Private); + EXPECT_STREQ("private", result.get()); + result = storage->Get(testKey, DataStorage_Persistent); + EXPECT_STREQ("value", result.get()); + + // Put of a previously-present key overwrites it (if of the same type) + NS_NAMED_LITERAL_CSTRING(newValue, "new"); + EXPECT_EQ(NS_OK, storage->Put(testKey, newValue, DataStorage_Persistent)); + result = storage->Get(testKey, DataStorage_Persistent); + EXPECT_STREQ("new", result.get()); + + // Removal should work + storage->Remove(testKey, DataStorage_Temporary); + result = storage->Get(testKey, DataStorage_Temporary); + EXPECT_TRUE(result.IsEmpty()); + // But removing one type shouldn't affect the others + result = storage->Get(testKey, DataStorage_Private); + EXPECT_STREQ("private", result.get()); + result = storage->Get(testKey, DataStorage_Persistent); + EXPECT_STREQ("new", result.get()); + // Test removing the other types as well + storage->Remove(testKey, DataStorage_Private); + result = storage->Get(testKey, DataStorage_Private); + EXPECT_TRUE(result.IsEmpty()); + storage->Remove(testKey, DataStorage_Persistent); + result = storage->Get(testKey, DataStorage_Persistent); + EXPECT_TRUE(result.IsEmpty()); +} + +TEST_F(psm_DataStorageTest, InputValidation) +{ + EXPECT_TRUE(dataWillPersist); + + // Keys may not have tabs or newlines + EXPECT_EQ(NS_ERROR_INVALID_ARG, + storage->Put(NS_LITERAL_CSTRING("key\thas tab"), testValue, + DataStorage_Persistent)); + nsCString result = storage->Get(NS_LITERAL_CSTRING("key\thas tab"), + DataStorage_Persistent); + EXPECT_TRUE(result.IsEmpty()); + EXPECT_EQ(NS_ERROR_INVALID_ARG, + storage->Put(NS_LITERAL_CSTRING("key has\nnewline"), testValue, + DataStorage_Persistent)); + result = storage->Get(NS_LITERAL_CSTRING("keyhas\nnewline"), + DataStorage_Persistent); + EXPECT_TRUE(result.IsEmpty()); + // Values may not have newlines + EXPECT_EQ(NS_ERROR_INVALID_ARG, + storage->Put(testKey, NS_LITERAL_CSTRING("value\nhas newline"), + DataStorage_Persistent)); + result = storage->Get(testKey, DataStorage_Persistent); + // Values may have tabs + EXPECT_TRUE(result.IsEmpty()); + EXPECT_EQ(NS_OK, storage->Put(testKey, + NS_LITERAL_CSTRING("val\thas tab; this is ok"), + DataStorage_Persistent)); + result = storage->Get(testKey, DataStorage_Persistent); + EXPECT_STREQ("val\thas tab; this is ok", result.get()); + + nsCString longKey("a"); + for (int i = 0; i < 8; i++) { + longKey.Append(longKey); + } + // A key of length 256 will work + EXPECT_EQ(NS_OK, storage->Put(longKey, testValue, DataStorage_Persistent)); + result = storage->Get(longKey, DataStorage_Persistent); + EXPECT_STREQ("value", result.get()); + longKey.Append("a"); + // A key longer than that will not work + EXPECT_EQ(NS_ERROR_INVALID_ARG, + storage->Put(longKey, testValue, DataStorage_Persistent)); + result = storage->Get(longKey, DataStorage_Persistent); + EXPECT_TRUE(result.IsEmpty()); + + nsCString longValue("a"); + for (int i = 0; i < 10; i++) { + longValue.Append(longValue); + } + // A value of length 1024 will work + EXPECT_EQ(NS_OK, storage->Put(testKey, longValue, DataStorage_Persistent)); + result = storage->Get(testKey, DataStorage_Persistent); + EXPECT_STREQ(longValue.get(), result.get()); + longValue.Append("a"); + // A value longer than that will not work + storage->Remove(testKey, DataStorage_Persistent); + EXPECT_EQ(NS_ERROR_INVALID_ARG, + storage->Put(testKey, longValue, DataStorage_Persistent)); + result = storage->Get(testKey, DataStorage_Persistent); + EXPECT_TRUE(result.IsEmpty()); +} + +TEST_F(psm_DataStorageTest, Eviction) +{ + EXPECT_TRUE(dataWillPersist); + + // Eviction is on a per-table basis. Tables shouldn't affect each other. + EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent)); + for (int i = 0; i < 1025; i++) { + EXPECT_EQ(NS_OK, storage->Put(nsPrintfCString("%d", i), + nsPrintfCString("%d", i), + DataStorage_Temporary)); + nsCString result = storage->Get(nsPrintfCString("%d", i), + DataStorage_Temporary); + EXPECT_STREQ(nsPrintfCString("%d", i).get(), result.get()); + } + // We don't know which entry got evicted, but we can count them. + int entries = 0; + for (int i = 0; i < 1025; i++) { + nsCString result = storage->Get(nsPrintfCString("%d", i), + DataStorage_Temporary); + if (!result.IsEmpty()) { + entries++; + } + } + EXPECT_EQ(entries, 1024); + nsCString result = storage->Get(testKey, DataStorage_Persistent); + EXPECT_STREQ("value", result.get()); +} + +TEST_F(psm_DataStorageTest, ClearPrivateData) +{ + EXPECT_TRUE(dataWillPersist); + + EXPECT_EQ(NS_OK, storage->Put(testKey, privateTestValue, + DataStorage_Private)); + nsCString result = storage->Get(testKey, DataStorage_Private); + EXPECT_STREQ("private", result.get()); + storage->Observe(nullptr, "last-pb-context-exited", nullptr); + result = storage->Get(testKey, DataStorage_Private); + EXPECT_TRUE(result.IsEmpty()); +} + +TEST_F(psm_DataStorageTest, Shutdown) +{ + EXPECT_TRUE(dataWillPersist); + + EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent)); + nsCString result = storage->Get(testKey, DataStorage_Persistent); + EXPECT_STREQ("value", result.get()); + // Get "now" (in days) close to when the data was last touched, so we won't + // get intermittent failures with the day not matching. + int64_t microsecondsPerDay = 24 * 60 * 60 * int64_t(PR_USEC_PER_SEC); + int32_t nowInDays = int32_t(PR_Now() / microsecondsPerDay); + storage->Observe(nullptr, "profile-before-change", nullptr); + nsCOMPtr backingFile; + EXPECT_EQ(NS_OK, NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(backingFile))); + const ::testing::TestInfo* const testInfo = + ::testing::UnitTest::GetInstance()->current_test_info(); + NS_ConvertUTF8toUTF16 testName(testInfo->name()); + EXPECT_EQ(NS_OK, backingFile->Append(testName)); + nsCOMPtr fileInputStream; + EXPECT_EQ(NS_OK, NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), + backingFile)); + nsCString data; + EXPECT_EQ(NS_OK, NS_ConsumeStream(fileInputStream, UINT32_MAX, data)); + // The data will be of the form 'test\t0\t\tvalue' + EXPECT_STREQ(nsPrintfCString("test\t0\t%d\tvalue\n", nowInDays).get(), + data.get()); +} diff --git a/security/manager/ssl/tests/gtest/DeserializeCertTest.cpp b/security/manager/ssl/tests/gtest/DeserializeCertTest.cpp new file mode 100644 index 000000000..868ecdba6 --- /dev/null +++ b/security/manager/ssl/tests/gtest/DeserializeCertTest.cpp @@ -0,0 +1,96 @@ +/* -*- 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 "gtest/gtest.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsSerializationHelper.h" + +// These tests verify that we can still deserialize old binary strings +// generated for security info. This is necessary because service workers +// stores these strings on disk. +// +// If you make a change and start breaking these tests, you will need to +// add a compat fix for loading the old versions. For things that affect +// the UUID, but do not break the rest of the format you can simply add +// another hack condition in nsBinaryInputStream::ReadObject(). If you +// change the overall format of the serialization then we will need more +// complex handling in the security info concrete classes. +// +// We would like to move away from this binary compatibility requirement +// in service workers. See bug 1248628. + +TEST(psm_DeserializeCert, gecko33) +{ + // Gecko 33+ vintage Security info serialized with UUIDs: + // - nsISupports 00000000-0000-0000-c000-000000000046 + // - nsISSLStatus fa9ba95b-ca3b-498a-b889-7c79cf28fee8 + // - nsIX509Cert f8ed8364-ced9-4c6e-86ba-48af53c393e6 + nsCString base64Serialization( + "FnhllAKWRHGAlo+ESXykKAAAAAAAAAAAwAAAAAAAAEaphjojH6pBabDSgSnsfLHeAAQAAgAAAAAAAAAAAAAAAAAAAAA" + "B4vFIJp5wRkeyPxAQ9RJGKPqbqVvKO0mKuIl8ec8o/uhmCjImkVxP+7sgiYWmMt8F+O2DZM7ZTG6GukivU8OT5gAAAAIAAAWpMII" + "FpTCCBI2gAwIBAgIQD4svsaKEC+QtqtsU2TF8ITANBgkqhkiG9w0BAQsFADBwMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUN" + "lcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMS8wLQYDVQQDEyZEaWdpQ2VydCBTSEEyIEhpZ2ggQXNzdXJhbmNlIFN" + "lcnZlciBDQTAeFw0xNTAyMjMwMDAwMDBaFw0xNjAzMDIxMjAwMDBaMGoxCzAJBgNVBAYTAlVTMRYwFAYDVQQHEw1TYW4gRnJhbmN" + "pc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRUwEwYDVQQKEwxGYXN0bHksIEluYy4xFzAVBgNVBAMTDnd3dy5naXRodWIuY29tMII" + "BIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+9WUCgrgUNwP/JC3cUefLAXeDpq8Ko/U8p8IRvny0Ri0I6Uq0t+RP/nF0LJ" + "Avda8QHYujdgeDTePepBX7+OiwBFhA0YO+rM3C2Z8IRaN/i9eLln+Yyc68+1z+E10s1EXdZrtDGvN6MHqygGsdfkXKfBLUJ1BZEh" + "s9sBnfcjq3kh5gZdBArdG9l5NpdmQhtceaFGsPiWuJxGxRzS4i95veUHWkhMpEYDEEBdcDGxqArvQCvzSlngdttQCfx8OUkBTb3B" + "A2okpTwwJfqPsxVetA6qR7UNc+fVb6KHwvm0bzi2rQ3xw3D/syRHwdMkpoVDQPCk43H9WufgfBKRen87dFwIDAQABo4ICPzCCAjs" + "wHwYDVR0jBBgwFoAUUWj/kK8CB3U8zNllZGKiErhZcjswHQYDVR0OBBYEFGS/RLNGCZvPWh1xSaIEcouINIQjMHsGA1UdEQR0MHK" + "CDnd3dy5naXRodWIuY29tggpnaXRodWIuY29tggwqLmdpdGh1Yi5jb22CCyouZ2l0aHViLmlvgglnaXRodWIuaW+CFyouZ2l0aHV" + "idXNlcmNvbnRlbnQuY29tghVnaXRodWJ1c2VyY29udGVudC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwM" + "BBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1oYS1zZXJ2ZXItZzMuY3J" + "sMDSgMqAwhi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1oYS1zZXJ2ZXItZzMuY3JsMEIGA1UdIAQ7MDkwNwYJYIZIAYb" + "9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgYMGCCsGAQUFBwEBBHcwdTAkBggrBgEFBQc" + "wAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME0GCCsGAQUFBzAChkFodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUN" + "lcnRTSEEySGlnaEFzc3VyYW5jZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQAc4dbVmuKvyI7" + "KZ4Txk+ZqcAYToJGKUIVaPL94e5SZGweUisjaCbplAOihnf6Mxt8n6vnuH2IsCaz2NRHqhdcosjT3CwAiJpJNkXPKWVL/txgdSTV" + "2cqB1GG4esFOalvI52dzn+J4fTIYZvNF+AtGyHSLm2XRXYZCw455laUKf6Sk9RDShDgUvzhOKL4GXfTwKXv12MyMknJybH8UCpjC" + "HZmFBVHMcUN/87HsQo20PdOekeEvkjrrMIxW+gxw22Yb67yF/qKgwrWr+43bLN709iyw+LWiU7sQcHL2xk9SYiWQDj2tYz2soObV" + "QYTJm0VUZMEVFhtALq46cx92Zu4vFwC8AAwAAAAABAQAA"); + + nsCOMPtr cert; + nsresult rv = NS_DeserializeObject(base64Serialization, getter_AddRefs(cert)); + ASSERT_EQ(NS_OK, rv); + ASSERT_TRUE(cert); +} + +TEST(psm_DeserializeCert, gecko46) +{ + // Gecko 46+ vintage Security info serialized with UUIDs: + // - nsISupports 00000000-0000-0000-c000-000000000046 + // - nsISSLStatus fa9ba95b-ca3b-498a-b889-7c79cf28fee8 + // - nsIX509Cert bdc3979a-5422-4cd5-8589-696b6e96ea83 + nsCString base64Serialization( + "FnhllAKWRHGAlo+ESXykKAAAAAAAAAAAwAAAAAAAAEaphjojH6pBabDSgSnsfLHeAAQAAgAAAAAAAAAAAAAAAAAAAAA" + "B4vFIJp5wRkeyPxAQ9RJGKPqbqVvKO0mKuIl8ec8o/uhmCjImkVxP+7sgiYWmMt8FvcOXmlQiTNWFiWlrbpbqgwAAAAIAAAWzMII" + "FrzCCBJegAwIBAgIQB3pdwzYjAfmJ/lT3+G8+ZDANBgkqhkiG9w0BAQsFADBwMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUN" + "lcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMS8wLQYDVQQDEyZEaWdpQ2VydCBTSEEyIEhpZ2ggQXNzdXJhbmNlIFN" + "lcnZlciBDQTAeFw0xNjAxMjAwMDAwMDBaFw0xNzA0MDYxMjAwMDBaMGoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybml" + "hMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRUwEwYDVQQKEwxGYXN0bHksIEluYy4xFzAVBgNVBAMTDnd3dy5naXRodWIuY29tMII" + "BIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+9WUCgrgUNwP/JC3cUefLAXeDpq8Ko/U8p8IRvny0Ri0I6Uq0t+RP/nF0LJ" + "Avda8QHYujdgeDTePepBX7+OiwBFhA0YO+rM3C2Z8IRaN/i9eLln+Yyc68+1z+E10s1EXdZrtDGvN6MHqygGsdfkXKfBLUJ1BZEh" + "s9sBnfcjq3kh5gZdBArdG9l5NpdmQhtceaFGsPiWuJxGxRzS4i95veUHWkhMpEYDEEBdcDGxqArvQCvzSlngdttQCfx8OUkBTb3B" + "A2okpTwwJfqPsxVetA6qR7UNc+fVb6KHwvm0bzi2rQ3xw3D/syRHwdMkpoVDQPCk43H9WufgfBKRen87dFwIDAQABo4ICSTCCAkU" + "wHwYDVR0jBBgwFoAUUWj/kK8CB3U8zNllZGKiErhZcjswHQYDVR0OBBYEFGS/RLNGCZvPWh1xSaIEcouINIQjMHsGA1UdEQR0MHK" + "CDnd3dy5naXRodWIuY29tggwqLmdpdGh1Yi5jb22CCmdpdGh1Yi5jb22CCyouZ2l0aHViLmlvgglnaXRodWIuaW+CFyouZ2l0aHV" + "idXNlcmNvbnRlbnQuY29tghVnaXRodWJ1c2VyY29udGVudC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwM" + "BBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1oYS1zZXJ2ZXItZzUuY3J" + "sMDSgMqAwhi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1oYS1zZXJ2ZXItZzUuY3JsMEwGA1UdIARFMEMwNwYJYIZIAYb" + "9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQICMIGDBggrBgEFBQcBAQR3MHU" + "wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBNBggrBgEFBQcwAoZBaHR0cDovL2NhY2VydHMuZGlnaWNlcnQ" + "uY29tL0RpZ2lDZXJ0U0hBMkhpZ2hBc3N1cmFuY2VTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQE" + "ATxbRdPg+o49+96/P+rbdp4ie+CGtfCgUubT/Z9C54k+BfQO0nbxVgCSM5WZQuLgo2Q+0lcxisod8zxZeU0j5wviQINwOln/iN89" + "Bx3VmDRynTe4CqhsAwOoO1ERmCAmsAJBwY/rNr4mK22p8erBrqMW0nYXYU5NFynI+pNTjojhKD4II8PNV8G2yMWwYOb/u4+WPzUA" + "HC9DpZdrWTEH/W69Cr/KxRqGsWPwpgMv2Wqav8jaT35JxqTXjOlhQqzo6fNn3eYOeCf4PkCxZKwckWjy10qDaRbjhwAMHAGj2TPr" + "idlvOj/7QyyX5m8up/1US8z1fRW4yoCSOt6V2bwuH6cAvAAMAAAAAAQEAAA=="); + + nsCOMPtr cert; + nsresult rv = NS_DeserializeObject(base64Serialization, getter_AddRefs(cert)); + ASSERT_EQ(NS_OK, rv); + ASSERT_TRUE(cert); +} diff --git a/security/manager/ssl/tests/gtest/MD4Test.cpp b/security/manager/ssl/tests/gtest/MD4Test.cpp new file mode 100644 index 000000000..1b635a757 --- /dev/null +++ b/security/manager/ssl/tests/gtest/MD4Test.cpp @@ -0,0 +1,76 @@ +/* -*- 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/. */ + +// This file tests the md4.c implementation. + +#include "gtest/gtest.h" +#include "md4.h" +#include "mozilla/Casting.h" +#include "mozilla/PodOperations.h" + +struct RFC1320TestParams +{ + const char* data; + const uint8_t expectedHash[16]; +}; + +static const RFC1320TestParams RFC1320_TEST_PARAMS[] = +{ + { + "", + { 0x31, 0xd6, 0xcf, 0xe0, 0xd1, 0x6a, 0xe9, 0x31, + 0xb7, 0x3c, 0x59, 0xd7, 0xe0, 0xc0, 0x89, 0xc0 } + }, + { + "a", + { 0xbd, 0xe5, 0x2c, 0xb3, 0x1d, 0xe3, 0x3e, 0x46, + 0x24, 0x5e, 0x05, 0xfb, 0xdb, 0xd6, 0xfb, 0x24 } + }, + { + "abc", + { 0xa4, 0x48, 0x01, 0x7a, 0xaf, 0x21, 0xd8, 0x52, + 0x5f, 0xc1, 0x0a, 0xe8, 0x7a, 0xa6, 0x72, 0x9d } + }, + { + "message digest", + { 0xd9, 0x13, 0x0a, 0x81, 0x64, 0x54, 0x9f, 0xe8, + 0x18, 0x87, 0x48, 0x06, 0xe1, 0xc7, 0x01, 0x4b } + }, + { + "abcdefghijklmnopqrstuvwxyz", + { 0xd7, 0x9e, 0x1c, 0x30, 0x8a, 0xa5, 0xbb, 0xcd, + 0xee, 0xa8, 0xed, 0x63, 0xdf, 0x41, 0x2d, 0xa9 }, + }, + { + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + { 0x04, 0x3f, 0x85, 0x82, 0xf2, 0x41, 0xdb, 0x35, + 0x1c, 0xe6, 0x27, 0xe1, 0x53, 0xe7, 0xf0, 0xe4 }, + }, + { + "12345678901234567890123456789012345678901234567890123456789012345678901234567890", + { 0xe3, 0x3b, 0x4d, 0xdc, 0x9c, 0x38, 0xf2, 0x19, + 0x9c, 0x3e, 0x7b, 0x16, 0x4f, 0xcc, 0x05, 0x36 }, + } +}; + +class psm_MD4 + : public ::testing::Test + , public ::testing::WithParamInterface +{ +}; + +TEST_P(psm_MD4, RFC1320TestValues) +{ + const RFC1320TestParams& params(GetParam()); + uint8_t actualHash[16]; + md4sum(mozilla::BitwiseCast(params.data), + strlen(params.data), actualHash); + EXPECT_TRUE(mozilla::PodEqual(actualHash, params.expectedHash)) + << "MD4 hashes aren't equal for input: '" << params.data << "'"; +} + +INSTANTIATE_TEST_CASE_P(psm_MD4, psm_MD4, + testing::ValuesIn(RFC1320_TEST_PARAMS)); diff --git a/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp b/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp new file mode 100644 index 000000000..2b7dc946c --- /dev/null +++ b/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp @@ -0,0 +1,337 @@ +/* -*- 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 "CertVerifier.h" +#include "OCSPCache.h" +#include "gtest/gtest.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/Casting.h" +#include "mozilla/Sprintf.h" +#include "nss.h" +#include "pkix/pkixtypes.h" +#include "pkixtestutil.h" +#include "prerr.h" +#include "secerr.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +using mozilla::NeckoOriginAttributes; + +template +inline Input +LiteralInput(const char(&valueString)[N]) +{ + // Ideally we would use mozilla::BitwiseCast() here rather than + // reinterpret_cast for better type checking, but the |N - 1| part trips + // static asserts. + return Input(reinterpret_cast(valueString)); +} + +const int MaxCacheEntries = 1024; + +class psm_OCSPCacheTest : public ::testing::Test +{ +protected: + psm_OCSPCacheTest() : now(Now()) { } + + static void SetUpTestCase() + { + NSS_NoDB_Init(nullptr); + } + + const Time now; + mozilla::psm::OCSPCache cache; +}; + +static void +PutAndGet(mozilla::psm::OCSPCache& cache, const CertID& certID, Result result, + Time time, + const NeckoOriginAttributes& originAttributes = NeckoOriginAttributes()) +{ + // The first time is thisUpdate. The second is validUntil. + // The caller is expecting the validUntil returned with Get + // to be equal to the passed-in time. Since these values will + // be different in practice, make thisUpdate less than validUntil. + Time thisUpdate(time); + ASSERT_EQ(Success, thisUpdate.SubtractSeconds(10)); + Result rv = cache.Put(certID, originAttributes, result, thisUpdate, time); + ASSERT_TRUE(rv == Success); + Result resultOut; + Time timeOut(Time::uninitialized); + ASSERT_TRUE(cache.Get(certID, originAttributes, resultOut, timeOut)); + ASSERT_EQ(result, resultOut); + ASSERT_EQ(time, timeOut); +} + +Input fakeIssuer1(LiteralInput("CN=issuer1")); +Input fakeKey000(LiteralInput("key000")); +Input fakeKey001(LiteralInput("key001")); +Input fakeSerial0000(LiteralInput("0000")); + +TEST_F(psm_OCSPCacheTest, TestPutAndGet) +{ + Input fakeSerial000(LiteralInput("000")); + Input fakeSerial001(LiteralInput("001")); + + SCOPED_TRACE(""); + PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial001), + Success, now); + Result resultOut; + Time timeOut(Time::uninitialized); + ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey001, fakeSerial000), + NeckoOriginAttributes(), resultOut, timeOut)); +} + +TEST_F(psm_OCSPCacheTest, TestVariousGets) +{ + SCOPED_TRACE(""); + for (int i = 0; i < MaxCacheEntries; i++) { + uint8_t serialBuf[8]; + snprintf(mozilla::BitwiseCast(serialBuf), sizeof(serialBuf), + "%04d", i); + Input fakeSerial; + ASSERT_EQ(Success, fakeSerial.Init(serialBuf, 4)); + Time timeIn(now); + ASSERT_EQ(Success, timeIn.AddSeconds(i)); + PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial), + Success, timeIn); + } + + Time timeIn(now); + Result resultOut; + Time timeOut(Time::uninitialized); + + // This will be at the end of the list in the cache + CertID cert0000(fakeIssuer1, fakeKey000, fakeSerial0000); + ASSERT_TRUE(cache.Get(cert0000, NeckoOriginAttributes(), resultOut, timeOut)); + ASSERT_EQ(Success, resultOut); + ASSERT_EQ(timeIn, timeOut); + // Once we access it, it goes to the front + ASSERT_TRUE(cache.Get(cert0000, NeckoOriginAttributes(), resultOut, timeOut)); + ASSERT_EQ(Success, resultOut); + ASSERT_EQ(timeIn, timeOut); + + // This will be in the middle + Time timeInPlus512(now); + ASSERT_EQ(Success, timeInPlus512.AddSeconds(512)); + + static const Input fakeSerial0512(LiteralInput("0512")); + CertID cert0512(fakeIssuer1, fakeKey000, fakeSerial0512); + ASSERT_TRUE(cache.Get(cert0512, NeckoOriginAttributes(), resultOut, timeOut)); + ASSERT_EQ(Success, resultOut); + ASSERT_EQ(timeInPlus512, timeOut); + ASSERT_TRUE(cache.Get(cert0512, NeckoOriginAttributes(), resultOut, timeOut)); + ASSERT_EQ(Success, resultOut); + ASSERT_EQ(timeInPlus512, timeOut); + + // We've never seen this certificate + static const Input fakeSerial1111(LiteralInput("1111")); + ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey000, fakeSerial1111), + NeckoOriginAttributes(), resultOut, timeOut)); +} + +TEST_F(psm_OCSPCacheTest, TestEviction) +{ + SCOPED_TRACE(""); + // By putting more distinct entries in the cache than it can hold, + // we cause the least recently used entry to be evicted. + for (int i = 0; i < MaxCacheEntries + 1; i++) { + uint8_t serialBuf[8]; + snprintf(mozilla::BitwiseCast(serialBuf), sizeof(serialBuf), + "%04d", i); + Input fakeSerial; + ASSERT_EQ(Success, fakeSerial.Init(serialBuf, 4)); + Time timeIn(now); + ASSERT_EQ(Success, timeIn.AddSeconds(i)); + PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial), + Success, timeIn); + } + + Result resultOut; + Time timeOut(Time::uninitialized); + ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey001, fakeSerial0000), + NeckoOriginAttributes(), resultOut, timeOut)); +} + +TEST_F(psm_OCSPCacheTest, TestNoEvictionForRevokedResponses) +{ + SCOPED_TRACE(""); + CertID notEvicted(fakeIssuer1, fakeKey000, fakeSerial0000); + Time timeIn(now); + PutAndGet(cache, notEvicted, Result::ERROR_REVOKED_CERTIFICATE, timeIn); + // By putting more distinct entries in the cache than it can hold, + // we cause the least recently used entry that isn't revoked to be evicted. + for (int i = 1; i < MaxCacheEntries + 1; i++) { + uint8_t serialBuf[8]; + snprintf(mozilla::BitwiseCast(serialBuf), sizeof(serialBuf), + "%04d", i); + Input fakeSerial; + ASSERT_EQ(Success, fakeSerial.Init(serialBuf, 4)); + Time timeIn(now); + ASSERT_EQ(Success, timeIn.AddSeconds(i)); + PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial), + Success, timeIn); + } + Result resultOut; + Time timeOut(Time::uninitialized); + ASSERT_TRUE(cache.Get(notEvicted, NeckoOriginAttributes(), resultOut, timeOut)); + ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, resultOut); + ASSERT_EQ(timeIn, timeOut); + + Input fakeSerial0001(LiteralInput("0001")); + CertID evicted(fakeIssuer1, fakeKey000, fakeSerial0001); + ASSERT_FALSE(cache.Get(evicted, NeckoOriginAttributes(), resultOut, timeOut)); +} + +TEST_F(psm_OCSPCacheTest, TestEverythingIsRevoked) +{ + SCOPED_TRACE(""); + Time timeIn(now); + // Fill up the cache with revoked responses. + for (int i = 0; i < MaxCacheEntries; i++) { + uint8_t serialBuf[8]; + snprintf(mozilla::BitwiseCast(serialBuf), sizeof(serialBuf), + "%04d", i); + Input fakeSerial; + ASSERT_EQ(Success, fakeSerial.Init(serialBuf, 4)); + Time timeIn(now); + ASSERT_EQ(Success, timeIn.AddSeconds(i)); + PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial), + Result::ERROR_REVOKED_CERTIFICATE, timeIn); + } + static const Input fakeSerial1025(LiteralInput("1025")); + CertID good(fakeIssuer1, fakeKey000, fakeSerial1025); + // This will "succeed", allowing verification to continue. However, + // nothing was actually put in the cache. + Time timeInPlus1025(timeIn); + ASSERT_EQ(Success, timeInPlus1025.AddSeconds(1025)); + Time timeInPlus1025Minus50(timeInPlus1025); + ASSERT_EQ(Success, timeInPlus1025Minus50.SubtractSeconds(50)); + Result result = cache.Put(good, NeckoOriginAttributes(), Success, timeInPlus1025Minus50, + timeInPlus1025); + ASSERT_EQ(Success, result); + Result resultOut; + Time timeOut(Time::uninitialized); + ASSERT_FALSE(cache.Get(good, NeckoOriginAttributes(), resultOut, timeOut)); + + static const Input fakeSerial1026(LiteralInput("1026")); + CertID revoked(fakeIssuer1, fakeKey000, fakeSerial1026); + // This will fail, causing verification to fail. + Time timeInPlus1026(timeIn); + ASSERT_EQ(Success, timeInPlus1026.AddSeconds(1026)); + Time timeInPlus1026Minus50(timeInPlus1026); + ASSERT_EQ(Success, timeInPlus1026Minus50.SubtractSeconds(50)); + result = cache.Put(revoked, NeckoOriginAttributes(), Result::ERROR_REVOKED_CERTIFICATE, + timeInPlus1026Minus50, timeInPlus1026); + ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, result); +} + +TEST_F(psm_OCSPCacheTest, VariousIssuers) +{ + SCOPED_TRACE(""); + Time timeIn(now); + static const Input fakeIssuer2(LiteralInput("CN=issuer2")); + static const Input fakeSerial001(LiteralInput("001")); + CertID subject(fakeIssuer1, fakeKey000, fakeSerial001); + PutAndGet(cache, subject, Success, now); + Result resultOut; + Time timeOut(Time::uninitialized); + ASSERT_TRUE(cache.Get(subject, NeckoOriginAttributes(), resultOut, timeOut)); + ASSERT_EQ(Success, resultOut); + ASSERT_EQ(timeIn, timeOut); + // Test that we don't match a different issuer DN + ASSERT_FALSE(cache.Get(CertID(fakeIssuer2, fakeKey000, fakeSerial001), + NeckoOriginAttributes(), resultOut, timeOut)); + // Test that we don't match a different issuer key + ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey001, fakeSerial001), + NeckoOriginAttributes(), resultOut, timeOut)); +} + +TEST_F(psm_OCSPCacheTest, Times) +{ + SCOPED_TRACE(""); + CertID certID(fakeIssuer1, fakeKey000, fakeSerial0000); + PutAndGet(cache, certID, Result::ERROR_OCSP_UNKNOWN_CERT, + TimeFromElapsedSecondsAD(100)); + PutAndGet(cache, certID, Success, TimeFromElapsedSecondsAD(200)); + // This should not override the more recent entry. + ASSERT_EQ(Success, + cache.Put(certID, NeckoOriginAttributes(), Result::ERROR_OCSP_UNKNOWN_CERT, + TimeFromElapsedSecondsAD(100), + TimeFromElapsedSecondsAD(100))); + Result resultOut; + Time timeOut(Time::uninitialized); + ASSERT_TRUE(cache.Get(certID, NeckoOriginAttributes(), resultOut, timeOut)); + // Here we see the more recent time. + ASSERT_EQ(Success, resultOut); + ASSERT_EQ(TimeFromElapsedSecondsAD(200), timeOut); + + // Result::ERROR_REVOKED_CERTIFICATE overrides everything + PutAndGet(cache, certID, Result::ERROR_REVOKED_CERTIFICATE, + TimeFromElapsedSecondsAD(50)); +} + +TEST_F(psm_OCSPCacheTest, NetworkFailure) +{ + SCOPED_TRACE(""); + CertID certID(fakeIssuer1, fakeKey000, fakeSerial0000); + PutAndGet(cache, certID, Result::ERROR_CONNECT_REFUSED, + TimeFromElapsedSecondsAD(100)); + PutAndGet(cache, certID, Success, TimeFromElapsedSecondsAD(200)); + // This should not override the already present entry. + ASSERT_EQ(Success, + cache.Put(certID, NeckoOriginAttributes(), Result::ERROR_CONNECT_REFUSED, + TimeFromElapsedSecondsAD(300), + TimeFromElapsedSecondsAD(350))); + Result resultOut; + Time timeOut(Time::uninitialized); + ASSERT_TRUE(cache.Get(certID, NeckoOriginAttributes(), resultOut, timeOut)); + ASSERT_EQ(Success, resultOut); + ASSERT_EQ(TimeFromElapsedSecondsAD(200), timeOut); + + PutAndGet(cache, certID, Result::ERROR_OCSP_UNKNOWN_CERT, + TimeFromElapsedSecondsAD(400)); + // This should not override the already present entry. + ASSERT_EQ(Success, + cache.Put(certID, NeckoOriginAttributes(), Result::ERROR_CONNECT_REFUSED, + TimeFromElapsedSecondsAD(500), + TimeFromElapsedSecondsAD(550))); + ASSERT_TRUE(cache.Get(certID, NeckoOriginAttributes(), resultOut, timeOut)); + ASSERT_EQ(Result::ERROR_OCSP_UNKNOWN_CERT, resultOut); + ASSERT_EQ(TimeFromElapsedSecondsAD(400), timeOut); + + PutAndGet(cache, certID, Result::ERROR_REVOKED_CERTIFICATE, + TimeFromElapsedSecondsAD(600)); + // This should not override the already present entry. + ASSERT_EQ(Success, + cache.Put(certID, NeckoOriginAttributes(), Result::ERROR_CONNECT_REFUSED, + TimeFromElapsedSecondsAD(700), + TimeFromElapsedSecondsAD(750))); + ASSERT_TRUE(cache.Get(certID, NeckoOriginAttributes(), resultOut, timeOut)); + ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, resultOut); + ASSERT_EQ(TimeFromElapsedSecondsAD(600), timeOut); +} + +TEST_F(psm_OCSPCacheTest, TestOriginAttributes) +{ + CertID certID(fakeIssuer1, fakeKey000, fakeSerial0000); + + SCOPED_TRACE(""); + NeckoOriginAttributes attrs; + attrs.mFirstPartyDomain.AssignLiteral("foo.com"); + PutAndGet(cache, certID, Success, now, attrs); + + Result resultOut; + Time timeOut(Time::uninitialized); + attrs.mFirstPartyDomain.AssignLiteral("bar.com"); + ASSERT_FALSE(cache.Get(certID, attrs, resultOut, timeOut)); + + // OCSP cache should not be isolated by containers. + attrs.mUserContextId = 1; + attrs.mFirstPartyDomain.AssignLiteral("foo.com"); + ASSERT_TRUE(cache.Get(certID, attrs, resultOut, timeOut)); +} diff --git a/security/manager/ssl/tests/gtest/README.txt b/security/manager/ssl/tests/gtest/README.txt new file mode 100644 index 000000000..0e5132269 --- /dev/null +++ b/security/manager/ssl/tests/gtest/README.txt @@ -0,0 +1,2 @@ +Please name all test cases in this directory with the prefix "psm". This makes +it easier to run all PSM related GTests at once. diff --git a/security/manager/ssl/tests/gtest/STSParserTest.cpp b/security/manager/ssl/tests/gtest/STSParserTest.cpp new file mode 100644 index 000000000..bedf57fea --- /dev/null +++ b/security/manager/ssl/tests/gtest/STSParserTest.cpp @@ -0,0 +1,144 @@ +/* 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 + +#include "gtest/gtest.h" +#include "nsNetUtil.h" +#include "nsISiteSecurityService.h" +#include "nsIURI.h" + +void +TestSuccess(const char* hdr, bool extraTokens, + uint64_t expectedMaxAge, bool expectedIncludeSubdomains, + nsISiteSecurityService* sss) +{ + nsCOMPtr dummyUri; + nsresult rv = NS_NewURI(getter_AddRefs(dummyUri), "https://foo.com/bar.html"); + ASSERT_TRUE(NS_SUCCEEDED(rv)) << "Failed to create URI"; + + uint64_t maxAge = 0; + bool includeSubdomains = false; + rv = sss->UnsafeProcessHeader(nsISiteSecurityService::HEADER_HSTS, dummyUri, + hdr, 0, &maxAge, &includeSubdomains, nullptr); + ASSERT_TRUE(NS_SUCCEEDED(rv)) << "Failed to process valid header: " << hdr; + + ASSERT_EQ(maxAge, expectedMaxAge) << "Did not correctly parse maxAge"; + EXPECT_EQ(includeSubdomains, expectedIncludeSubdomains) << + "Did not correctly parse presence/absence of includeSubdomains"; + + if (extraTokens) { + EXPECT_EQ(rv, NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA) << + "Extra tokens were expected when parsing, but were not encountered."; + } else { + EXPECT_EQ(rv, NS_OK) << "Unexpected tokens found during parsing."; + } + + printf("%s\n", hdr); +} + +void TestFailure(const char* hdr, + nsISiteSecurityService* sss) +{ + nsCOMPtr dummyUri; + nsresult rv = NS_NewURI(getter_AddRefs(dummyUri), "https://foo.com/bar.html"); + ASSERT_TRUE(NS_SUCCEEDED(rv)) << "Failed to create URI"; + + rv = sss->UnsafeProcessHeader(nsISiteSecurityService::HEADER_HSTS, dummyUri, + hdr, 0, nullptr, nullptr, nullptr); + ASSERT_TRUE(NS_FAILED(rv)) << "Parsed invalid header: " << hdr; + + printf("%s\n", hdr); +} + +TEST(psm_STSParser, Test) +{ + nsresult rv; + + // grab handle to the service + nsCOMPtr sss; + sss = do_GetService("@mozilla.org/ssservice;1", &rv); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // *** parsing tests + printf("*** Attempting to parse valid STS headers ...\n"); + + // SHOULD SUCCEED: + TestSuccess("max-age=100", false, 100, false, sss); + TestSuccess("max-age =100", false, 100, false, sss); + TestSuccess(" max-age=100", false, 100, false, sss); + TestSuccess("max-age = 100 ", false, 100, false, sss); + TestSuccess("max-age = \"100\" ", false, 100, false, sss); + TestSuccess("max-age=\"100\"", false, 100, false, sss); + TestSuccess(" max-age =\"100\" ", false, 100, false, sss); + TestSuccess("\tmax-age\t=\t\"100\"\t", false, 100, false, sss); + TestSuccess("max-age = 100 ", false, 100, false, sss); + + TestSuccess("maX-aGe=100", false, 100, false, sss); + TestSuccess("MAX-age =100", false, 100, false, sss); + TestSuccess("max-AGE=100", false, 100, false, sss); + TestSuccess("Max-Age = 100 ", false, 100, false, sss); + TestSuccess("MAX-AGE = 100 ", false, 100, false, sss); + + TestSuccess("max-age=100;includeSubdomains", false, 100, true, sss); + TestSuccess("max-age=100\t; includeSubdomains", false, 100, true, sss); + TestSuccess(" max-age=100; includeSubdomains", false, 100, true, sss); + TestSuccess("max-age = 100 ; includeSubdomains", false, 100, true, sss); + TestSuccess("max-age = 100 ; includeSubdomains", + false, 100, true, sss); + + TestSuccess("maX-aGe=100; includeSUBDOMAINS", false, 100, true, sss); + TestSuccess("MAX-age =100; includeSubDomains", false, 100, true, sss); + TestSuccess("max-AGE=100; iNcLuDeSuBdoMaInS", false, 100, true, sss); + TestSuccess("Max-Age = 100; includesubdomains ", false, 100, true, sss); + TestSuccess("INCLUDESUBDOMAINS;MaX-AgE = 100 ", false, 100, true, sss); + // Turns out, the actual directive is entirely optional (hence the + // trailing semicolon) + TestSuccess("max-age=100;includeSubdomains;", true, 100, true, sss); + + // these are weird tests, but are testing that some extended syntax is + // still allowed (but it is ignored) + TestSuccess("max-age=100 ; includesubdomainsSomeStuff", + true, 100, false, sss); + TestSuccess("\r\n\t\t \tcompletelyUnrelated = foobar; max-age= 34520103" + "\t \t; alsoUnrelated;asIsThis;\tincludeSubdomains\t\t \t", + true, 34520103, true, sss); + TestSuccess("max-age=100; unrelated=\"quoted \\\"thingy\\\"\"", + true, 100, false, sss); + + // SHOULD FAIL: + printf("* Attempting to parse invalid STS headers (should not parse)...\n"); + // invalid max-ages + TestFailure("max-age", sss); + TestFailure("max-age ", sss); + TestFailure("max-age=p", sss); + TestFailure("max-age=*1p2", sss); + TestFailure("max-age=.20032", sss); + TestFailure("max-age=!20032", sss); + TestFailure("max-age==20032", sss); + + // invalid headers + TestFailure("foobar", sss); + TestFailure("maxage=100", sss); + TestFailure("maxa-ge=100", sss); + TestFailure("max-ag=100", sss); + TestFailure("includesubdomains", sss); + TestFailure(";", sss); + TestFailure("max-age=\"100", sss); + // The max-age directive here doesn't conform to the spec, so it MUST + // be ignored. Consequently, the REQUIRED max-age directive is not + // present in this header, and so it is invalid. + TestFailure("max-age=100, max-age=200; includeSubdomains", sss); + TestFailure("max-age=100 includesubdomains", sss); + TestFailure("max-age=100 bar foo", sss); + TestFailure("max-age=100randomstuffhere", sss); + // All directives MUST appear only once in an STS header field. + TestFailure("max-age=100; max-age=200", sss); + TestFailure("includeSubdomains; max-age=200; includeSubdomains", sss); + TestFailure("max-age=200; includeSubdomains; includeSubdomains", sss); + // The includeSubdomains directive is valueless. + TestFailure("max-age=100; includeSubdomains=unexpected", sss); + // LWS must have at least one space or horizontal tab + TestFailure("\r\nmax-age=200", sss); +} diff --git a/security/manager/ssl/tests/gtest/TLSIntoleranceTest.cpp b/security/manager/ssl/tests/gtest/TLSIntoleranceTest.cpp new file mode 100644 index 000000000..65f5257fb --- /dev/null +++ b/security/manager/ssl/tests/gtest/TLSIntoleranceTest.cpp @@ -0,0 +1,568 @@ +/* -*- 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 psm_TLSIntoleranceTest : public ::testing::Test +{ +protected: + nsSSLIOLayerHelpers helpers; +}; + +TEST_F(psm_TLSIntoleranceTest, FullFallbackProcess) +{ + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, helpers.mVersionFallbackLimit); + + // No adjustment made when there is no entry for the site. + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCipherStatusUnknown, strongCipherStatus); + + ASSERT_TRUE( + helpers.rememberStrongCiphersFailed( + HOST, PORT, SSL_ERROR_NO_CYPHER_OVERLAP)); + ASSERT_EQ(SSL_ERROR_NO_CYPHER_OVERLAP, + helpers.getIntoleranceReason(HOST, PORT)); + } + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); + + ASSERT_FALSE(helpers.rememberStrongCiphersFailed(HOST, PORT, 0)); + ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT, + range.min, range.max, 0)); + } + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); + + ASSERT_FALSE(helpers.rememberStrongCiphersFailed(HOST, PORT, 0)); + ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT, + range.min, range.max, 0)); + } + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); + + ASSERT_FALSE(helpers.rememberStrongCiphersFailed(HOST, PORT, 0)); + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, + range.min, range.max, 0)); + } + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_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(psm_TLSIntoleranceTest, DisableFallbackWithHighLimit) +{ + // 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; + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2, + 0)); + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_1, + 0)); + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_0, + 0)); +} + +TEST_F(psm_TLSIntoleranceTest, FallbackLimitBelowMin) +{ + // check that we still respect the minimum version, + // when it is higher than the fallback limit + ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_TLS_1_1, + SSL_LIBRARY_VERSION_TLS_1_2, + 0)); + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); + ASSERT_EQ(StrongCipherStatusUnknown, strongCipherStatus); + } + + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_TLS_1_1, + SSL_LIBRARY_VERSION_TLS_1_1, + 0)); +} + +TEST_F(psm_TLSIntoleranceTest, TolerantOverridesIntolerant1) +{ + ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_1, + 0)); + helpers.rememberTolerantAtVersion(HOST, PORT, SSL_LIBRARY_VERSION_TLS_1_1); + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); +} + +TEST_F(psm_TLSIntoleranceTest, TolerantOverridesIntolerant2) +{ + ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_1, + 0)); + helpers.rememberTolerantAtVersion(HOST, PORT, SSL_LIBRARY_VERSION_TLS_1_2); + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); +} + +TEST_F(psm_TLSIntoleranceTest, IntolerantDoesNotOverrideTolerant) +{ + // No adjustment made when there is no entry for the site. + helpers.rememberTolerantAtVersion(HOST, PORT, SSL_LIBRARY_VERSION_TLS_1_1); + // false because we reached the floor set by rememberTolerantAtVersion. + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_1, + 0)); + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); +} + +TEST_F(psm_TLSIntoleranceTest, PortIsRelevant) +{ + helpers.rememberTolerantAtVersion(HOST, 1, SSL_LIBRARY_VERSION_TLS_1_2); + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, 1, + SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2, + 0)); + ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, 2, + SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2, + 0)); + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, 1, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + } + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, 2, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); + } +} + +TEST_F(psm_TLSIntoleranceTest, IntoleranceReasonInitial) +{ + 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(psm_TLSIntoleranceTest, IntoleranceReasonStored) +{ + helpers.rememberIntolerantAtVersion(HOST, 1, SSL_LIBRARY_VERSION_TLS_1_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_TLS_1_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(psm_TLSIntoleranceTest, IntoleranceReasonCleared) +{ + ASSERT_EQ(0, helpers.getIntoleranceReason(HOST, 1)); + + helpers.rememberIntolerantAtVersion(HOST, 1, SSL_LIBRARY_VERSION_TLS_1_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)); +} + +TEST_F(psm_TLSIntoleranceTest, StrongCiphersFailed) +{ + helpers.mVersionFallbackLimit = SSL_LIBRARY_VERSION_TLS_1_1; + + ASSERT_TRUE(helpers.rememberStrongCiphersFailed(HOST, PORT, 0)); + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_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_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_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_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_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(psm_TLSIntoleranceTest, StrongCiphersFailedAt1_1) +{ + helpers.mVersionFallbackLimit = SSL_LIBRARY_VERSION_TLS_1_0; + + // No adjustment made when there is no entry for the site. + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_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_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_TRUE(helpers.rememberStrongCiphersFailed(HOST, PORT, 0)); + } + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_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_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); + } +} + +TEST_F(psm_TLSIntoleranceTest, StrongCiphersFailedWithHighLimit) +{ + // 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, 0)); + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2, + 0)); + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_1, + 0)); + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_0, + 0)); +} + +TEST_F(psm_TLSIntoleranceTest, TolerantDoesNotOverrideWeakCiphersFallback) +{ + ASSERT_TRUE(helpers.rememberStrongCiphersFailed(HOST, PORT, 0)); + // No adjustment made when intolerant is zero. + helpers.rememberTolerantAtVersion(HOST, PORT, SSL_LIBRARY_VERSION_TLS_1_1); + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); +} + +TEST_F(psm_TLSIntoleranceTest, WeakCiphersFallbackDoesNotOverrideTolerant) +{ + // 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, 0)); + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); +} + +TEST_F(psm_TLSIntoleranceTest, TLSForgetIntolerance) +{ + { + ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2, + 0)); + + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); + ASSERT_EQ(StrongCipherStatusUnknown, strongCipherStatus); + } + + { + helpers.forgetIntolerance(HOST, PORT); + + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCipherStatusUnknown, strongCipherStatus); + } +} + +TEST_F(psm_TLSIntoleranceTest, TLSForgetStrongCipherFailed) +{ + { + ASSERT_TRUE(helpers.rememberStrongCiphersFailed(HOST, PORT, 0)); + + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_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_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(StrongCipherStatusUnknown, strongCipherStatus); + } +} + +TEST_F(psm_TLSIntoleranceTest, TLSDontForgetTolerance) +{ + { + helpers.rememberTolerantAtVersion(HOST, PORT, SSL_LIBRARY_VERSION_TLS_1_1); + + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); + } + + { + ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2, + 0)); + + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); + } + + { + helpers.forgetIntolerance(HOST, PORT); + + SSLVersionRange range = { SSL_LIBRARY_VERSION_TLS_1_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); + } +} + +TEST_F(psm_TLSIntoleranceTest, TLSPerSiteFallbackLimit) +{ + NS_NAMED_LITERAL_CSTRING(example_com, "example.com"); + NS_NAMED_LITERAL_CSTRING(example_net, "example.net"); + NS_NAMED_LITERAL_CSTRING(example_org, "example.org"); + + helpers.mVersionFallbackLimit = SSL_LIBRARY_VERSION_TLS_1_0; + + ASSERT_FALSE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_FALSE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_0)); + ASSERT_FALSE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_FALSE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_0)); + ASSERT_FALSE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_FALSE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_0)); + + helpers.mVersionFallbackLimit = SSL_LIBRARY_VERSION_TLS_1_2; + + ASSERT_TRUE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_0)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_0)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_0)); + + helpers.setInsecureFallbackSites(example_com); + + ASSERT_FALSE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_FALSE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_0)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_0)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_0)); + + helpers.setInsecureFallbackSites(NS_LITERAL_CSTRING("example.com,example.net")); + + ASSERT_FALSE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_FALSE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_0)); + ASSERT_FALSE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_FALSE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_0)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_0)); + + helpers.setInsecureFallbackSites(example_net); + + ASSERT_TRUE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_0)); + ASSERT_FALSE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_FALSE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_0)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_0)); + + helpers.setInsecureFallbackSites(EmptyCString()); + + ASSERT_TRUE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_com, SSL_LIBRARY_VERSION_TLS_1_0)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_net, SSL_LIBRARY_VERSION_TLS_1_0)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_2)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_1)); + ASSERT_TRUE(helpers.fallbackLimitReached(example_org, SSL_LIBRARY_VERSION_TLS_1_0)); +} diff --git a/security/manager/ssl/tests/gtest/moz.build b/security/manager/ssl/tests/gtest/moz.build new file mode 100644 index 000000000..735ab2971 --- /dev/null +++ b/security/manager/ssl/tests/gtest/moz.build @@ -0,0 +1,29 @@ +# -*- Mode: python; 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/. + +SOURCES += [ + 'CertDBTest.cpp', + 'DataStorageTest.cpp', + 'DeserializeCertTest.cpp', + 'MD4Test.cpp', + 'OCSPCacheTest.cpp', + 'STSParserTest.cpp', + 'TLSIntoleranceTest.cpp', +] + +LOCAL_INCLUDES += [ + '/security/certverifier', + '/security/manager/ssl', + '/security/pkix/include', + '/security/pkix/test/lib', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul-gtest' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] -- cgit v1.2.3