blob: 1970c6964705515dd6e4f893f541509ff6f91081 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/os_crypt/sync/os_crypt.h"
#include <array>
#include <string>
#include "base/functional/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "components/os_crypt/sync/key_storage_linux.h"
#include "components/os_crypt/sync/os_crypt_mocker_linux.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
std::unique_ptr<KeyStorageLinux> GetNullKeyStorage() {
return nullptr;
}
class OSCryptLinuxTest : public testing::Test {
public:
OSCryptLinuxTest() = default;
OSCryptLinuxTest(const OSCryptLinuxTest&) = delete;
OSCryptLinuxTest& operator=(const OSCryptLinuxTest&) = delete;
~OSCryptLinuxTest() override = default;
void SetUp() override {
OSCryptMockerLinux::SetUp();
OSCrypt::SetEncryptionPasswordForTesting("something");
}
void TearDown() override { OSCryptMockerLinux::TearDown(); }
};
TEST_F(OSCryptLinuxTest, VerifyV10) {
const std::string originaltext = "hello";
std::string ciphertext;
std::string decipheredtext;
OSCrypt::SetEncryptionPasswordForTesting("peanuts");
ASSERT_TRUE(OSCrypt::EncryptString(originaltext, &ciphertext));
OSCrypt::SetEncryptionPasswordForTesting("not_peanuts");
ciphertext = ciphertext.substr(3).insert(0, "v10");
ASSERT_TRUE(OSCrypt::DecryptString(ciphertext, &decipheredtext));
ASSERT_EQ(originaltext, decipheredtext);
}
TEST_F(OSCryptLinuxTest, VerifyV11) {
const std::string originaltext = "hello";
std::string ciphertext;
std::string decipheredtext;
OSCrypt::SetEncryptionPasswordForTesting(std::string());
ASSERT_TRUE(OSCrypt::EncryptString(originaltext, &ciphertext));
ASSERT_EQ(ciphertext.substr(0, 3), "v11");
ASSERT_TRUE(OSCrypt::DecryptString(ciphertext, &decipheredtext));
ASSERT_EQ(originaltext, decipheredtext);
}
TEST_F(OSCryptLinuxTest, IsEncryptionAvailable) {
EXPECT_TRUE(OSCrypt::IsEncryptionAvailable());
OSCrypt::ClearCacheForTesting();
// Mock the GetKeyStorage function.
OSCrypt::UseMockKeyStorageForTesting(base::BindOnce(&GetNullKeyStorage));
EXPECT_FALSE(OSCrypt::IsEncryptionAvailable());
}
TEST_F(OSCryptLinuxTest, SetRawEncryptionKey) {
const std::string originaltext = "hello";
std::string ciphertext;
std::string decipheredtext;
// Encrypt with not_peanuts and save the raw encryption key.
OSCrypt::SetEncryptionPasswordForTesting("not_peanuts");
ASSERT_TRUE(OSCrypt::EncryptString(originaltext, &ciphertext));
ASSERT_EQ(ciphertext.substr(0, 3), "v11");
std::string raw_key = OSCrypt::GetRawEncryptionKey();
ASSERT_FALSE(raw_key.empty());
// Clear the cached encryption key.
OSCrypt::ClearCacheForTesting();
// Set the raw encryption key and make sure decryption works.
OSCrypt::SetRawEncryptionKey(raw_key);
ASSERT_TRUE(OSCrypt::DecryptString(ciphertext, &decipheredtext));
ASSERT_EQ(originaltext, decipheredtext);
}
// Because of crbug.com/1195256, there might be data that is encrypted with an
// empty key. These should remain decryptable, even when a proper key is
// available.
TEST_F(OSCryptLinuxTest, DecryptWhenEncryptionKeyIsEmpty) {
base::HistogramTester histogram_tester;
const std::string originaltext = "hello";
std::string ciphertext;
std::string decipheredtext;
// Encrypt a value using "" as the key.
OSCrypt::SetEncryptionPasswordForTesting("");
ASSERT_TRUE(OSCrypt::EncryptString(originaltext, &ciphertext));
// Set a proper encryption key.
OSCrypt::ClearCacheForTesting();
OSCrypt::SetEncryptionPasswordForTesting("key");
// The text is decryptable.
ASSERT_TRUE(OSCrypt::DecryptString(ciphertext, &decipheredtext));
EXPECT_EQ(originaltext, decipheredtext);
histogram_tester.ExpectUniqueSample("OSCrypt.Linux.DecryptedWithEmptyKey",
true, 1);
}
struct KeyDerivationKnownAnswer {
const char* password;
std::array<uint8_t, 16> answer;
};
TEST_F(OSCryptLinuxTest, KeyDerivationKnownAnswers) {
// Known answers from PBKDF2-HMAC-SHA1 producing 128 bits of output with 1
// round and salted with "saltysalt".
// TODO(https://crbug.com/380912393): re-enable clang-format for this block.
// clang-format off
constexpr auto kCases = std::to_array<KeyDerivationKnownAnswer>({
{
// The hardcoded V10 obfuscation key.
.password = "peanuts",
.answer = {
0xfd, 0x62, 0x1f, 0xe5, 0xa2, 0xb4, 0x02, 0x53,
0x9d, 0xfa, 0x14, 0x7c, 0xa9, 0x27, 0x27, 0x78,
},
},
{
// The empty password fallback obfuscation key.
.password = "",
.answer = {
0xd0, 0xd0, 0xec, 0x9c, 0x7d, 0x77, 0xd4, 0x3a,
0xc5, 0x41, 0x87, 0xfa, 0x48, 0x18, 0xd1, 0x7f,
},
},
{
// An example V11 key generated from a random password.
.password = "zxqfb",
.answer = {
0xb7, 0x56, 0x30, 0x74, 0x74, 0xb0, 0x0d, 0xa3,
0x55, 0xf7, 0x73, 0xf0, 0x2f, 0x86, 0x1a, 0xe4,
},
},
});
// clang-format on
for (const auto& c : kCases) {
OSCrypt::SetEncryptionPasswordForTesting(c.password);
std::string r = OSCrypt::GetRawEncryptionKey();
EXPECT_EQ(c.answer, base::as_byte_span(r));
}
}
TEST_F(OSCryptLinuxTest, EncryptDecryptKnownAnswers) {
OSCrypt::SetEncryptionPasswordForTesting("zxqfb");
const std::string kInput = "Hello, World! This is a longer string.";
// The input string, encrypted using AES-128-CBC, with the "V11" version
// prefix and with a PKCS#5 padding block at the end.
// clang-format off
constexpr auto kExpectedCiphertext = std::to_array<uint8_t>({
0x76, 0x31, 0x31,
0x77, 0x21, 0x00, 0x60, 0x87, 0x09, 0xe8, 0x94,
0xde, 0x63, 0x4f, 0x80, 0x36, 0x40, 0xb7, 0xdd,
0x80, 0xe1, 0x2c, 0x6f, 0x9a, 0xb6, 0x6b, 0xba,
0x93, 0xaf, 0xec, 0xb5, 0x89, 0xe6, 0x12, 0x28,
0xf0, 0x85, 0xa2, 0xbb, 0xf9, 0x30, 0x30, 0x87,
0xf8, 0xd1, 0x10, 0x4e, 0x97, 0xe1, 0x75, 0x73,
});
// clang-format on
std::string ciphertext;
ASSERT_TRUE(OSCrypt::EncryptString(kInput, &ciphertext));
EXPECT_EQ(kExpectedCiphertext, base::as_byte_span(ciphertext));
std::string plaintext;
ASSERT_TRUE(OSCrypt::DecryptString(ciphertext, &plaintext));
EXPECT_EQ(kInput, plaintext);
}
} // namespace