blob: e5982c6fa909e219073550ba41f27e96413f79ef [file] [log] [blame]
// Copyright 2023 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/async/common/encryptor.h"
#include <algorithm>
#include <vector>
#include "base/containers/span.h"
#include "base/test/gtest_util.h"
#include "build/build_config.h"
#include "components/os_crypt/async/common/encryptor.h"
#include "components/os_crypt/sync/os_crypt.h"
#include "components/os_crypt/sync/os_crypt_mocker.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#include <wincrypt.h>
#include "base/win/scoped_localalloc.h"
#endif // BUILDFLAG(IS_WIN)
namespace os_crypt_async {
namespace {
#if BUILDFLAG(IS_WIN)
// Utility function to encrypt data using the raw DPAPI interface.
bool EncryptStringWithDPAPI(const std::string& plaintext,
std::string& ciphertext) {
DATA_BLOB input = {};
input.pbData =
const_cast<BYTE*>(reinterpret_cast<const BYTE*>(plaintext.data()));
input.cbData = static_cast<DWORD>(plaintext.length());
BOOL result = FALSE;
DATA_BLOB output = {};
result =
::CryptProtectData(&input, /*szDataDescr=*/L"",
/*pOptionalEntropy=*/nullptr, /*pvReserved=*/nullptr,
/*pPromptStruct=*/nullptr, /*dwFlags=*/0, &output);
if (!result) {
return false;
}
auto local_alloc = base::win::TakeLocalAlloc(output.pbData);
static_assert(sizeof(std::string::value_type) == 1);
ciphertext.assign(
reinterpret_cast<std::string::value_type*>(local_alloc.get()),
output.cbData);
return true;
}
#endif // BUILDFLAG(IS_WIN)
} // namespace
class EncryptorTestBase : public ::testing::Test {
protected:
// This is here so it can access the private methods in Encryptor via friends.
const Encryptor GetTestEncryptor() { return Encryptor(); }
};
class EncryptorTestWithOSCrypt : public EncryptorTestBase {
protected:
void SetUp() override { OSCryptMocker::SetUp(); }
void TearDown() override {
OSCryptMocker::TearDown();
#if BUILDFLAG(IS_WIN)
OSCrypt::ResetStateForTesting();
#endif // BUILDFLAG(IS_WIN)
}
};
using EncryptorTest = EncryptorTestWithOSCrypt;
TEST_F(EncryptorTest, StringInterface) {
const Encryptor encryptor = GetTestEncryptor();
std::string plaintext = "secrets";
std::string ciphertext;
EXPECT_TRUE(encryptor.EncryptString(plaintext, &ciphertext));
std::string decrypted;
EXPECT_TRUE(encryptor.DecryptString(ciphertext, &decrypted));
EXPECT_EQ(plaintext, decrypted);
}
TEST_F(EncryptorTest, SpanInterface) {
const Encryptor encryptor = GetTestEncryptor();
std::string plaintext = "secrets";
auto ciphertext = encryptor.EncryptString(plaintext);
ASSERT_TRUE(ciphertext);
auto decrypted = encryptor.DecryptData(*ciphertext);
ASSERT_TRUE(decrypted);
EXPECT_EQ(plaintext, *decrypted);
}
TEST_F(EncryptorTest, EncryptStringDecryptSpan) {
const Encryptor encryptor = GetTestEncryptor();
std::string plaintext = "secrets";
std::string ciphertext;
EXPECT_TRUE(encryptor.EncryptString(plaintext, &ciphertext));
auto decrypted =
encryptor.DecryptData(base::as_bytes(base::make_span(ciphertext)));
ASSERT_TRUE(decrypted);
EXPECT_EQ(plaintext.size(), decrypted->size());
ASSERT_TRUE(
std::equal(plaintext.cbegin(), plaintext.cend(), decrypted->cbegin()));
}
TEST_F(EncryptorTest, EncryptSpanDecryptString) {
const Encryptor encryptor = GetTestEncryptor();
std::string plaintext = "secrets";
auto ciphertext = encryptor.EncryptString(plaintext);
ASSERT_TRUE(ciphertext);
std::string decrypted;
EXPECT_TRUE(encryptor.DecryptString(
std::string(ciphertext->begin(), ciphertext->end()), &decrypted));
EXPECT_EQ(plaintext.size(), decrypted.size());
EXPECT_TRUE(
std::equal(plaintext.cbegin(), plaintext.cend(), decrypted.cbegin()));
}
TEST_F(EncryptorTest, EncryptEmpty) {
const Encryptor encryptor = GetTestEncryptor();
auto ciphertext = encryptor.EncryptString(std::string());
ASSERT_TRUE(ciphertext);
auto decrypted = encryptor.DecryptData(*ciphertext);
ASSERT_TRUE(decrypted);
EXPECT_TRUE(decrypted->empty());
}
// In a behavior change on Windows, Decrypt/Encrypt of empty data results in a
// success and an empty buffer. This was already the behavior on non-Windows so
// this change makes it consistent.
TEST_F(EncryptorTest, DecryptEmpty) {
const Encryptor encryptor = GetTestEncryptor();
auto plaintext = encryptor.DecryptData({});
ASSERT_TRUE(plaintext);
EXPECT_TRUE(plaintext->empty());
}
// Non-Windows platforms can decrypt random data fine.
#if BUILDFLAG(IS_WIN)
TEST_F(EncryptorTest, DecryptInvalid) {
const Encryptor encryptor = GetTestEncryptor();
std::vector<uint8_t> invalid_cipher(100);
for (size_t c = 0u; c < invalid_cipher.size(); c++) {
invalid_cipher[c] = c;
}
auto plaintext = encryptor.DecryptData(invalid_cipher);
ASSERT_FALSE(plaintext);
}
#endif // BUILDFLAG(IS_WIN)
// Encryptor can decrypt data encrypted with OSCrypt.
TEST_F(EncryptorTest, DecryptFallback) {
std::string ciphertext;
EXPECT_TRUE(OSCrypt::EncryptString("secret", &ciphertext));
const Encryptor encryptor = GetTestEncryptor();
std::string decrypted;
// Fallback to OSCrypt takes place.
EXPECT_TRUE(encryptor.DecryptString(ciphertext, &decrypted));
EXPECT_EQ("secret", decrypted);
}
#if BUILDFLAG(IS_WIN)
// Encryptor should still decrypt data encrypted using DPAPI (pre-m79) by fall
// back to OSCrypt.
TEST_F(EncryptorTest, AncientFallback) {
std::string ciphertext;
EXPECT_TRUE(EncryptStringWithDPAPI("secret", ciphertext));
std::string decrypted;
const Encryptor encryptor = GetTestEncryptor();
// Encryptor can still decrypt very old DPAPI data.
EXPECT_TRUE(encryptor.DecryptString(ciphertext, &decrypted));
EXPECT_EQ("secret", decrypted);
}
#endif // BUILDFLAG(IS_WIN)
} // namespace os_crypt_async