// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/os_crypt/os_crypt.h"

#include <stddef.h>

#include <algorithm>
#include <iterator>
#include <memory>

#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/os_crypt/key_storage_linux.h"
#include "crypto/encryptor.h"
#include "crypto/symmetric_key.h"

namespace {

// Salt for Symmetric key derivation.
const char kSalt[] = "saltysalt";

// Key size required for 128 bit AES.
const size_t kDerivedKeySizeInBits = 128;

// Constant for Symmetic key derivation.
const size_t kEncryptionIterations = 1;

// Size of initialization vector for AES 128-bit.
const size_t kIVBlockSizeAES128 = 16;

// Password version. V10 means that the hardcoded password will be used.
// V11 means that a password is/will be stored using an OS-level library (e.g
// Libsecret). V11 will not be used if such a library is not available.
// Used for array indexing.
enum Version {
  V10 = 0,
  V11 = 1,
};

// Prefix for cipher text returned by obfuscation version.  We prefix the
// ciphertext with this string so that future data migration can detect
// this and migrate to full encryption without data loss.
const char kObfuscationPrefix[][4] = {
    "v10", "v11",
};

struct Cache {
  std::unique_ptr<KeyStorageLinux> key_storage_cache;
  std::unique_ptr<std::string> password_v10_cache;
  std::unique_ptr<std::string> password_v11_cache;
  bool is_key_storage_cached;
  bool is_password_v11_cached;
};

base::LazyInstance<Cache>::Leaky g_cache = LAZY_INSTANCE_INITIALIZER;

// Lazy acquisition and caching of a KeyStorage. Will be null if no service is
// found.
KeyStorageLinux* GetKeyStorage() {
  if (!g_cache.Get().is_key_storage_cached) {
    g_cache.Get().is_key_storage_cached = true;
    g_cache.Get().key_storage_cache = KeyStorageLinux::CreateService();
  }
  return g_cache.Get().key_storage_cache.get();
}

// Returns a cached string of "peanuts".
std::string* GetPasswordV10() {
  if (!g_cache.Get().password_v10_cache.get())
    g_cache.Get().password_v10_cache.reset(new std::string("peanuts"));
  return g_cache.Get().password_v10_cache.get();
}

// Caches and returns the password from the KeyStorage or null if there is no
// service.
std::string* GetPasswordV11() {
  if (!g_cache.Get().is_password_v11_cached) {
    g_cache.Get().is_password_v11_cached = true;
    g_cache.Get().password_v11_cache.reset(
        GetKeyStorage() ? new std::string(GetKeyStorage()->GetKey()) : nullptr);
  }
  return g_cache.Get().password_v11_cache.get();
}

// Pointer to a function that creates and returns the |KeyStorage| instance to
// be used. The function maintains ownership of the pointer.
KeyStorageLinux* (*g_key_storage_provider)() = &GetKeyStorage;

// Pointers to functions that return a password for deriving the encryption key.
// One function for each supported password version (see Version enum).
std::string* (*g_get_password[])() = {
    &GetPasswordV10, &GetPasswordV11,
};

// Generates a newly allocated SymmetricKey object based on a password.
// Ownership of the key is passed to the caller. Returns null key if a key
// generation error occurs.
std::unique_ptr<crypto::SymmetricKey> GetEncryptionKey(Version version) {
  std::string salt(kSalt);

  std::string* password = g_get_password[version]();
  if (!password)
    return nullptr;

  // Create an encryption key from our password and salt.
  std::unique_ptr<crypto::SymmetricKey> encryption_key(
      crypto::SymmetricKey::DeriveKeyFromPassword(
          crypto::SymmetricKey::AES, *password, salt, kEncryptionIterations,
          kDerivedKeySizeInBits));
  DCHECK(encryption_key);

  return encryption_key;
}

}  // namespace

// static
bool OSCrypt::EncryptString16(const base::string16& plaintext,
                              std::string* ciphertext) {
  return EncryptString(base::UTF16ToUTF8(plaintext), ciphertext);
}

// static
bool OSCrypt::DecryptString16(const std::string& ciphertext,
                              base::string16* plaintext) {
  std::string utf8;
  if (!DecryptString(ciphertext, &utf8))
    return false;

  *plaintext = base::UTF8ToUTF16(utf8);
  return true;
}

// static
bool OSCrypt::EncryptString(const std::string& plaintext,
                            std::string* ciphertext) {
  if (plaintext.empty()) {
    ciphertext->clear();
    return true;
  }

  // If a |KeyStorage| is available, use a password backed by the |KeyStorage|.
  // Otherwise use the hardcoded password.
  Version version = g_key_storage_provider() ? Version::V11 : Version::V10;

  std::unique_ptr<crypto::SymmetricKey> encryption_key(
      GetEncryptionKey(version));
  if (!encryption_key)
    return false;

  std::string iv(kIVBlockSizeAES128, ' ');
  crypto::Encryptor encryptor;
  if (!encryptor.Init(encryption_key.get(), crypto::Encryptor::CBC, iv))
    return false;

  if (!encryptor.Encrypt(plaintext, ciphertext))
    return false;

  // Prefix the cipher text with version information.
  ciphertext->insert(0, kObfuscationPrefix[version]);
  return true;
}

// static
bool OSCrypt::DecryptString(const std::string& ciphertext,
                            std::string* plaintext) {
  if (ciphertext.empty()) {
    plaintext->clear();
    return true;
  }

  // Check that the incoming ciphertext was encrypted and with what version.
  // Credit card numbers are current legacy unencrypted data, so false match
  // with prefix won't happen.
  Version version;
  if (base::StartsWith(ciphertext, kObfuscationPrefix[Version::V10],
                       base::CompareCase::SENSITIVE)) {
    version = Version::V10;
  } else if (base::StartsWith(ciphertext, kObfuscationPrefix[Version::V11],
                              base::CompareCase::SENSITIVE)) {
    version = Version::V11;
  } else {
    // If the prefix is not found then we'll assume we're dealing with
    // old data saved as clear text and we'll return it directly.
    *plaintext = ciphertext;
    return true;
  }

  std::unique_ptr<crypto::SymmetricKey> encryption_key(
      GetEncryptionKey(version));
  if (!encryption_key)
    return false;

  std::string iv(kIVBlockSizeAES128, ' ');
  crypto::Encryptor encryptor;
  if (!encryptor.Init(encryption_key.get(), crypto::Encryptor::CBC, iv))
    return false;

  // Strip off the versioning prefix before decrypting.
  std::string raw_ciphertext =
      ciphertext.substr(strlen(kObfuscationPrefix[version]));

  if (!encryptor.Decrypt(raw_ciphertext, plaintext))
    return false;

  return true;
}

// static
void OSCrypt::SetStore(const std::string& store_type) {
  // Changing the targeted password store makes no sense after initializing.
  DCHECK(!g_cache.Get().is_key_storage_cached);

  KeyStorageLinux::SetStore(store_type);
}

void UseMockKeyStorageForTesting(KeyStorageLinux* (*get_key_storage_mock)(),
                                 std::string* (*get_password_v11_mock)()) {
  // Save the real implementation to restore it later.
  static bool is_get_password_saved = false;
  static std::string* (*get_password_save[arraysize(g_get_password)])();
  if (!is_get_password_saved) {
    std::copy(std::begin(g_get_password), std::end(g_get_password),
              std::begin(get_password_save));
    is_get_password_saved = true;
  }

  if (get_key_storage_mock && get_password_v11_mock) {
    // Bypass calling KeyStorage::CreateService and caching of the key for V11
    g_get_password[Version::V11] = get_password_v11_mock;
    // OSCrypt will determine the encryption version by checking if a
    // |KeyStorage| instance can be created. Enable V11 by returning the mock.
    g_key_storage_provider = get_key_storage_mock;
  } else {
    // Restore real implementation
    std::copy(std::begin(get_password_save), std::end(get_password_save),
              std::begin(g_get_password));
  }
}
