/* 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 "CacheCrypto.h" #include "gtest/gtest.h" #include "mozilla/Preferences.h" #include "nsCOMPtr.h" #include "nsIX509CertDB.h" #include "nsServiceManagerUtils.h" #include "nsTArray.h" using namespace mozilla; using namespace mozilla::net; namespace { // CacheCrypto needs NSS (PK11) and a loaded key. Getting the cert DB service // initializes NSS; InitForTesting() then loads or generates the key without // depending on the "once"-mirrored enabled pref. Returns the usable instance, // or null if setup failed. static already_AddRefed InitCryptoForTest() { nsCOMPtr certDB(do_GetService(NS_X509CERTDB_CONTRACTID)); EXPECT_TRUE(certDB); CacheCrypto::InitForTesting(); return CacheCrypto::GetInstanceOrNull(); } } // namespace TEST(CacheCrypto, RoundTrip) { RefPtr crypto = InitCryptoForTest(); ASSERT_TRUE(crypto); // Length deliberately not a multiple of the AES block size. const char* msg = "The quick brown fox jumps over the lazy dog -- 0123456789"; const uint32_t len = strlen(msg); nsTArray block; block.SetLength(len + CacheCrypto::kBlockOverhead); nsTArray roundtrip; roundtrip.SetLength(len); for (uint64_t blockNumber : {uint64_t(0), uint64_t(1), uint64_t(7), uint64_t(12345), CacheCrypto::kMetadataBlockNumber}) { ASSERT_EQ(NS_OK, crypto->EncryptBlock(blockNumber, reinterpret_cast(msg), len, block.Elements())); // Ciphertext differs from plaintext. EXPECT_NE(0, memcmp(block.Elements(), msg, len)); ASSERT_EQ(NS_OK, crypto->DecryptBlock(blockNumber, block.Elements(), len, roundtrip.Elements())); EXPECT_EQ(0, memcmp(roundtrip.Elements(), msg, len)); } CacheCrypto::Shutdown(); } TEST(CacheCrypto, TamperAndWrongBlockFail) { RefPtr crypto = InitCryptoForTest(); ASSERT_TRUE(crypto); const char* msg = "authenticated payload"; const uint32_t len = strlen(msg); nsTArray block; block.SetLength(len + CacheCrypto::kBlockOverhead); nsTArray out; out.SetLength(len); ASSERT_EQ(NS_OK, crypto->EncryptBlock(3, reinterpret_cast(msg), len, block.Elements())); // Decrypting with a different block number fails: the block number is bound // as additional authenticated data. EXPECT_NE(NS_OK, crypto->DecryptBlock(4, block.Elements(), len, out.Elements())); // Tampered ciphertext fails the AEAD tag check. nsTArray tampered = block.Clone(); tampered[0] ^= 0x01; EXPECT_NE(NS_OK, crypto->DecryptBlock(3, tampered.Elements(), len, out.Elements())); // The untouched block at its own block number still decrypts. EXPECT_EQ(NS_OK, crypto->DecryptBlock(3, block.Elements(), len, out.Elements())); EXPECT_EQ(0, memcmp(out.Elements(), msg, len)); CacheCrypto::Shutdown(); } TEST(CacheCrypto, WrongKeyFails) { // Encrypt a block with the session's key. RefPtr crypto = InitCryptoForTest(); ASSERT_TRUE(crypto); const char* msg = "secret cache contents"; const uint32_t len = strlen(msg); nsTArray block; block.SetLength(len + CacheCrypto::kBlockOverhead); ASSERT_EQ(NS_OK, crypto->EncryptBlock(0, reinterpret_cast(msg), len, block.Elements())); // Model a later session whose key pref is empty / different: clearing the key // pref makes Init() generate a fresh (different) key. This covers both "the // key pref is empty" and "the key does not match". CacheCrypto::Shutdown(); Preferences::SetCString("browser.cache.disk.encryption.key", ""_ns); CacheCrypto::InitForTesting(); RefPtr crypto2 = CacheCrypto::GetInstanceOrNull(); ASSERT_TRUE(crypto2); // Decrypting the block written with the old key must fail (AEAD auth). nsTArray out; out.SetLength(len); EXPECT_NE(NS_OK, crypto2->DecryptBlock(0, block.Elements(), len, out.Elements())); CacheCrypto::Shutdown(); } TEST(CacheCrypto, FreshNoncePerEncryption) { RefPtr crypto = InitCryptoForTest(); ASSERT_TRUE(crypto); const char* msg = "identical plaintext, identical block number"; const uint32_t len = strlen(msg); nsTArray b1, b2; b1.SetLength(len + CacheCrypto::kBlockOverhead); b2.SetLength(len + CacheCrypto::kBlockOverhead); ASSERT_EQ(NS_OK, crypto->EncryptBlock(0, reinterpret_cast(msg), len, b1.Elements())); ASSERT_EQ(NS_OK, crypto->EncryptBlock(0, reinterpret_cast(msg), len, b2.Elements())); // Same key/block/plaintext, but a fresh random nonce per encryption means the // ciphertext (and nonce) differ. This is what prevents keystream/nonce reuse // when a block is rewritten. EXPECT_NE(0, memcmp(b1.Elements(), b2.Elements(), len + CacheCrypto::kBlockOverhead)); CacheCrypto::Shutdown(); }