blob: c4dab9b385c57cbcf5c1bda7a518044a298ea804 [file] [log] [blame]
// Copyright 2014 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 <stddef.h>
#include "base/command_line.h"
#include "base/debug/leak_annotations.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/no_destructor.h"
#include "base/strings/string_view_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "build/build_config.h"
#include "components/os_crypt/sync/keychain_password_mac.h"
#include "components/os_crypt/sync/os_crypt_metrics.h"
#include "components/os_crypt/sync/os_crypt_switches.h"
#include "crypto/aes_cbc.h"
#include "crypto/apple/keychain.h"
#include "crypto/apple/mock_keychain.h"
#include "crypto/kdf.h"
#include "crypto/subtle_passkey.h"
namespace os_crypt {
class EncryptionKeyCreationUtil;
}
namespace {
// Prefix for cypher text returned by current encryption version. We prefix
// the cypher text with this string so that future data migration can detect
// this and migrate to different encryption without data loss.
constexpr char kObfuscationPrefixV10[] = "v10";
constexpr std::array<uint8_t, crypto::aes_cbc::kBlockSize> kIv{
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
};
} // namespace
namespace OSCrypt {
bool EncryptString16(const std::u16string& plaintext, std::string* ciphertext) {
return OSCryptImpl::GetInstance()->EncryptString16(plaintext, ciphertext);
}
bool DecryptString16(const std::string& ciphertext, std::u16string* plaintext) {
return OSCryptImpl::GetInstance()->DecryptString16(ciphertext, plaintext);
}
bool EncryptString(const std::string& plaintext, std::string* ciphertext) {
return OSCryptImpl::GetInstance()->EncryptString(plaintext, ciphertext);
}
bool DecryptString(const std::string& ciphertext, std::string* plaintext) {
return OSCryptImpl::GetInstance()->DecryptString(ciphertext, plaintext);
}
void UseMockKeychainForTesting(bool use_mock) {
OSCryptImpl::GetInstance()->UseMockKeychainForTesting(use_mock);
}
void UseLockedMockKeychainForTesting(bool use_locked) {
OSCryptImpl::GetInstance()->UseLockedMockKeychainForTesting(use_locked);
}
std::string GetRawEncryptionKey() {
return OSCryptImpl::GetInstance()->GetRawEncryptionKey();
}
void SetRawEncryptionKey(const std::string& key) {
OSCryptImpl::GetInstance()->SetRawEncryptionKey(key);
}
bool IsEncryptionAvailable() {
return OSCryptImpl::GetInstance()->IsEncryptionAvailable();
}
} // namespace OSCrypt
// static
OSCryptImpl* OSCryptImpl::GetInstance() {
return base::Singleton<OSCryptImpl,
base::LeakySingletonTraits<OSCryptImpl>>::get();
}
OSCryptImpl::OSCryptImpl() = default;
OSCryptImpl::~OSCryptImpl() = default;
std::unique_ptr<crypto::apple::Keychain> OSCryptImpl::GetKeychain() const {
if (use_mock_keychain_ || base::CommandLine::ForCurrentProcess()->HasSwitch(
os_crypt::switches::kUseMockKeychain)) {
return std::make_unique<crypto::apple::MockKeychain>();
}
return crypto::apple::Keychain::DefaultKeychain();
}
bool OSCryptImpl::DeriveKey() {
base::AutoLock auto_lock(OSCryptImpl::GetLock());
// Fast fail when there's no key and derivation already failed in an earlier
// call to DeriveKey().
if (!try_keychain_ && !key_) {
return false;
}
// Fast fail if this object is pretending to have a locked keychain.
// TODO(https://crbug.com/389737048): Replace this with a setter on the mock
// keychain once it's possible to inject a mock keychain.
if (use_mock_keychain_ && use_locked_mock_keychain_) {
return false;
}
// If the key's already present, we are done.
if (key_) {
return true;
}
// Do the actual key derivation.
auto keychain = GetKeychain();
KeychainPassword encryptor_password(*keychain);
std::string password = encryptor_password.GetPassword();
// At this point, whether `encryptor_password.GetPassword()` succeeded or
// failed, the keychain has been tried. Never try it again.
try_keychain_ = false;
if (password.empty()) {
return false;
}
static constexpr auto kSalt =
std::to_array<uint8_t>({'s', 'a', 'l', 't', 'y', 's', 'a', 'l', 't'});
static constexpr size_t kIterations = 1003;
std::array<uint8_t, kDerivedKeySize> key;
crypto::kdf::DeriveKeyPbkdf2HmacSha1({.iterations = kIterations},
base::as_byte_span(password), kSalt, key,
crypto::SubtlePassKey{});
key_ = key;
return true;
}
std::string OSCryptImpl::GetRawEncryptionKey() {
return DeriveKey() ? std::string(base::as_string_view(*key_)) : std::string();
}
void OSCryptImpl::SetRawEncryptionKey(const std::string& raw_key) {
base::AutoLock auto_lock(OSCryptImpl::GetLock());
CHECK(!key_);
// Only an input of exactly `kDerivedKeySize` is acceptable for the key.
auto input = base::as_byte_span(raw_key).to_fixed_extent<kDerivedKeySize>();
if (input) {
key_ = std::array<uint8_t, kDerivedKeySize>();
base::span(*key_).copy_from(*input);
}
// This method is used over IPC to configure OSCryptImpl instances in
// sandboxed helper processes. Those helper processes don't have access to
// the keychain anyway, and if they try to access it, they'll crash.
// Therefore, never try the keychain in this mode, even when given an empty
// key.
try_keychain_ = false;
}
bool OSCryptImpl::EncryptString16(const std::u16string& plaintext,
std::string* ciphertext) {
return EncryptString(base::UTF16ToUTF8(plaintext), ciphertext);
}
bool OSCryptImpl::DecryptString16(const std::string& ciphertext,
std::u16string* plaintext) {
std::string utf8;
if (!DecryptString(ciphertext, &utf8)) {
return false;
}
*plaintext = base::UTF8ToUTF16(utf8);
return true;
}
bool OSCryptImpl::EncryptString(const std::string& plaintext,
std::string* ciphertext) {
if (plaintext.empty()) {
*ciphertext = std::string();
return true;
}
if (!DeriveKey()) {
VLOG(1) << "Key derivation failed";
return false;
}
// Prefix the cypher text with version information.
*ciphertext = kObfuscationPrefixV10;
ciphertext->append(base::as_string_view(
crypto::aes_cbc::Encrypt(*key_, kIv, base::as_byte_span(plaintext))));
return true;
}
bool OSCryptImpl::DecryptString(const std::string& ciphertext,
std::string* plaintext) {
if (ciphertext.empty()) {
*plaintext = std::string();
return true;
}
// Check that the incoming cyphertext was indeed encrypted with the expected
// version. 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.
// Credit card numbers are current legacy data, so false match with prefix
// won't happen.
const os_crypt::EncryptionPrefixVersion encryption_version =
ciphertext.find(kObfuscationPrefixV10) == 0
? os_crypt::EncryptionPrefixVersion::kVersion10
: os_crypt::EncryptionPrefixVersion::kNoVersion;
os_crypt::LogEncryptionVersion(encryption_version);
if (encryption_version == os_crypt::EncryptionPrefixVersion::kNoVersion) {
return false;
}
if (!DeriveKey()) {
VLOG(1) << "Key derivation failed";
return false;
}
// Strip off the versioning prefix before decrypting.
base::span<const uint8_t> raw_ciphertext =
base::as_byte_span(ciphertext).subspan(strlen(kObfuscationPrefixV10));
std::optional<std::vector<uint8_t>> maybe_plain =
crypto::aes_cbc::Decrypt(*key_, kIv, base::as_byte_span(raw_ciphertext));
if (!maybe_plain) {
VLOG(1) << "Decryption failed";
return false;
}
plaintext->assign(base::as_string_view(*maybe_plain));
return true;
}
bool OSCryptImpl::IsEncryptionAvailable() {
return DeriveKey();
}
void OSCryptImpl::UseMockKeychainForTesting(bool use_mock) {
use_mock_keychain_ = use_mock;
if (!use_mock_keychain_) {
use_locked_mock_keychain_ = false;
}
}
void OSCryptImpl::UseLockedMockKeychainForTesting(bool use_locked) {
use_locked_mock_keychain_ = use_locked;
if (use_locked_mock_keychain_) {
use_mock_keychain_ = true;
}
}
// static
base::Lock& OSCryptImpl::GetLock() {
static base::NoDestructor<base::Lock> os_crypt_lock;
return *os_crypt_lock;
}