chromium/components/os_crypt/async/common/encryptor_unittest.cc

// 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/functional/callback_helpers.h"
#include "base/test/gtest_util.h"
#include "build/build_config.h"
#include "components/os_crypt/async/common/encryptor.h"
#include "components/os_crypt/async/common/encryptor.mojom.h"
#include "components/os_crypt/sync/os_crypt.h"
#include "components/os_crypt/sync/os_crypt_mocker.h"
#include "crypto/random.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_LINUX)
#include "components/os_crypt/sync/key_storage_linux.h"
#endif

#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)

// Helper function to verify that decryption using OSCrypt failed. This is
// platform dependent, as Windows will fail, but other platforms will return the
// ciphertext back.
[[nodiscard]] bool MaybeVerifyFailedDecryptOperation(
    const std::optional<std::string>& decrypted,
    base::span<const uint8_t> ciphertext) {}

}  // namespace

enum class TestType {};

const auto kTestCases =;

class EncryptorTestBase : public ::testing::Test {};

class EncryptorTestWithOSCrypt : public EncryptorTestBase {};

class EncryptorTest : public EncryptorTestWithOSCrypt,
                      public ::testing::WithParamInterface<TestType> {};

TEST_P(EncryptorTest, StringInterface) {}

TEST_P(EncryptorTest, SpanInterface) {}

TEST_P(EncryptorTest, EncryptStringDecryptSpan) {}

TEST_P(EncryptorTest, EncryptSpanDecryptString) {}

TEST_P(EncryptorTest, EncryptDecryptString16) {}

TEST_P(EncryptorTest, EncryptEmpty) {}

// 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_P(EncryptorTest, DecryptEmpty) {}

// Non-Windows platforms can decrypt random data fine.
#if BUILDFLAG(IS_WIN)
TEST_P(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_P(EncryptorTest, DecryptFallback) {}

// Encryptor can decrypt data encrypted with OSCrypt.
TEST_P(EncryptorTest, Decrypt16Fallback) {}

#if BUILDFLAG(IS_WIN)
// Encryptor should still decrypt data encrypted using DPAPI (pre-m79) by fall
// back to OSCrypt.
TEST_P(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)

INSTANTIATE_TEST_SUITE_P();

// This test verifies various combinations of multiple keys in a keyring, to
// make sure they are all handled correctly.
TEST_F(EncryptorTestBase, MultipleKeys) {}

TEST_F(EncryptorTestWithOSCrypt, EmptyandNonEmpty) {}

TEST_F(EncryptorTestWithOSCrypt, ShortCiphertext) {}

// These two tests verify the fallback to OSCrypt::IsEncryptionAvailable
// functions correctly. When there is no OSCrypt mocker in place, encryption is
// not available if the keyring is empty.
TEST_F(EncryptorTestBase, IsEncryptionAvailableFallback) {}

TEST_F(EncryptorTestWithOSCrypt, IsEncryptionAvailableFallback) {}

TEST_F(EncryptorTestBase, IsEncryptionAvailable) {}

#if BUILDFLAG(IS_WIN)

// This test verifies that data encrypted with OSCrypt can successfully be
// decrypted by an Encryptor loaded with the same key with
// Algorithm::kAES256GCM.
TEST_F(EncryptorTestBase, AlgorithmDecryptCompatibility) {
  std::string ciphertext;
  std::string ciphertext16;
  auto random_key = GenerateRandomTestKey(kKeyLength);
  // Set the OSCrypt key to the fixed key.
  OSCrypt::SetRawEncryptionKey(
      std::string(random_key.begin(), random_key.end()));

  // OSCrypt will now encrypt using this random key.
  EXPECT_TRUE(OSCrypt::EncryptString("secret", &ciphertext));
  EXPECT_TRUE(OSCrypt::EncryptString16(u"secret16", &ciphertext16));

  // Reset OSCrypt so it cannot know the key, so fallback will fail.
  OSCrypt::ResetStateForTesting();
  OSCrypt::UseMockKeyForTesting(true);

  // Verify that OSCrypt can no longer decrypt this data.
  std::string plaintext;
  EXPECT_FALSE(OSCrypt::DecryptString(ciphertext, &plaintext));

  // Set up a test Encryptor that can decrypt the data.
  Encryptor::KeyRing key_ring;

  // "v10" is the OSCrypt tag for data encrypted with Algorithm::kAES256GCM. See
  // `kEncryptionVersionPrefix` in os_crypt_win.cc.
  constexpr char kEncryptionVersionPrefix[] = "v10";
  key_ring.emplace(kEncryptionVersionPrefix,
                   Encryptor::Key(random_key, mojom::Algorithm::kAES256GCM));

  // Construct an Encryptor with the same key that was used by OSCrypt to
  // encrypt the data.
  const Encryptor encryptor =
      GetEncryptor(std::move(key_ring), kEncryptionVersionPrefix);

  // The data should decrypt.
  EXPECT_TRUE(encryptor.DecryptString(ciphertext, &plaintext));
  std::u16string plaintext16;
  EXPECT_TRUE(encryptor.DecryptString16(ciphertext16, &plaintext16));

  EXPECT_EQ("secret", plaintext);
  EXPECT_EQ(u"secret16", plaintext16);

  // Reset OSCrypt for the next test.
  OSCrypt::ResetStateForTesting();
}

// This test verifies that data encrypted with an Encryptor loaded with the same
// key as OSCrypt and Algorithm::kAES256GCM can successfully be decrypted by
// OSCrypt.
TEST_F(EncryptorTestBase, AlgorithmEncryptCompatibility) {
  // From os_crypt_win.cc
  auto random_key = GenerateRandomTestKey(kKeyLength);

  // Set up a test Encryptor that can encrypt the data.
  Encryptor::KeyRing key_ring;
  // "v10" is the OSCrypt tag for data encrypted with Algorithm::kAES256GCM. See
  // `kEncryptionVersionPrefix` in os_crypt_win.cc.
  constexpr char kEncryptionVersionPrefix[] = "v10";
  key_ring.emplace(kEncryptionVersionPrefix,
                   Encryptor::Key(random_key, mojom::Algorithm::kAES256GCM));

  // Construct an Encryptor with this key. The encryption provider tag will be
  // "v10" to match OSCrypt's encryption. Encrypt the data.
  const Encryptor encryptor =
      GetEncryptor(std::move(key_ring), kEncryptionVersionPrefix);
  auto ciphertext = encryptor.EncryptString("secret");
  EXPECT_TRUE(ciphertext);
  std::string ciphertext16;
  EXPECT_TRUE(encryptor.EncryptString16(u"secret16", &ciphertext16));

  // OSCrypt should not be able to decrypt this yet, as it does not have the
  // key.
  OSCrypt::UseMockKeyForTesting(true);
  std::string plaintext;
  std::u16string plaintext16;
  EXPECT_FALSE(OSCrypt::DecryptString(
      std::string(ciphertext->begin(), ciphertext->end()), &plaintext));
  EXPECT_FALSE(OSCrypt::DecryptString16(ciphertext16, &plaintext16));

  // Set the OSCrypt key to the fixed key.
  OSCrypt::ResetStateForTesting();
  OSCrypt::SetRawEncryptionKey(
      std::string(random_key.begin(), random_key.end()));

  // OSCrypt should now be able to decrypt using this key.
  EXPECT_TRUE(OSCrypt::DecryptString(
      std::string(ciphertext->begin(), ciphertext->end()), &plaintext));
  EXPECT_EQ("secret", plaintext);

  EXPECT_TRUE(OSCrypt::DecryptString16(ciphertext16, &plaintext16));
  EXPECT_EQ(u"secret16", plaintext16);

  // Reset OSCrypt for the next test.
  OSCrypt::ResetStateForTesting();
}

#endif  // BUILDFLAG(IS_WIN)

// Test that Clone respects the option to a key that is os_crypt sync
// compatible.
TEST_F(EncryptorTestBase, Clone) {}

class EncryptorTraitsTest : public EncryptorTestBase {};

TEST_F(EncryptorTraitsTest, TraitsRoundTrip) {}

}  // namespace os_crypt_async