blob: 4f96304179a2d2261b1be837778bd209584fb6be [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 <stddef.h>
#include <algorithm>
#include <iterator>
#include <memory>
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/strings/string_view_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "components/os_crypt/sync/key_storage_config_linux.h"
#include "components/os_crypt/sync/key_storage_linux.h"
#include "components/os_crypt/sync/os_crypt_metrics.h"
#include "crypto/aes_cbc.h"
#include "crypto/kdf.h"
namespace {
// Prefixes for cypher 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. kObfuscationPrefixV10
// means that the hardcoded password will be used. kObfuscationPrefixV11 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.
constexpr char kObfuscationPrefixV10[] = "v10";
constexpr char kObfuscationPrefixV11[] = "v11";
constexpr crypto::kdf::Pbkdf2HmacSha1Params kParams{
.iterations = 1,
};
const auto kSalt = base::byte_span_from_cstring("saltysalt");
// clang-format off
// PBKDF2-HMAC-SHA1(1 iteration, key = "peanuts", salt = "saltysalt")
constexpr auto kV10Key = std::to_array<uint8_t>({
0xfd, 0x62, 0x1f, 0xe5, 0xa2, 0xb4, 0x02, 0x53,
0x9d, 0xfa, 0x14, 0x7c, 0xa9, 0x27, 0x27, 0x78,
});
// PBKDF2-HMAC-SHA1(1 iteration, key = "", salt = "saltysalt")
constexpr auto kEmptyKey = std::to_array<uint8_t>({
0xd0, 0xd0, 0xec, 0x9c, 0x7d, 0x77, 0xd4, 0x3a,
0xc5, 0x41, 0x87, 0xfa, 0x48, 0x18, 0xd1, 0x7f,
});
const std::array<uint8_t, crypto::aes_cbc::kBlockSize> kIv{
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
};
// clang-format on
// The UMA metric name for whether the false was decryptable with an empty key.
constexpr char kMetricDecryptedWithEmptyKey[] =
"OSCrypt.Linux.DecryptedWithEmptyKey";
} // namespace
namespace OSCrypt {
void SetConfig(std::unique_ptr<os_crypt::Config> config) {
OSCryptImpl::GetInstance()->SetConfig(std::move(config));
}
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);
}
std::string GetRawEncryptionKey() {
return OSCryptImpl::GetInstance()->GetRawEncryptionKey();
}
void SetRawEncryptionKey(const std::string& key) {
OSCryptImpl::GetInstance()->SetRawEncryptionKey(key);
}
bool IsEncryptionAvailable() {
return OSCryptImpl::GetInstance()->IsEncryptionAvailable();
}
void UseMockKeyStorageForTesting(
base::OnceCallback<std::unique_ptr<KeyStorageLinux>()>
storage_provider_factory) {
OSCryptImpl::GetInstance()->UseMockKeyStorageForTesting(
std::move(storage_provider_factory));
}
void ClearCacheForTesting() {
OSCryptImpl::GetInstance()->ClearCacheForTesting();
}
void SetEncryptionPasswordForTesting(const std::string& password) {
OSCryptImpl::GetInstance()->SetEncryptionPasswordForTesting(password);
}
} // namespace OSCrypt
OSCryptImpl* OSCryptImpl::GetInstance() {
return base::Singleton<OSCryptImpl,
base::LeakySingletonTraits<OSCryptImpl>>::get();
}
OSCryptImpl::OSCryptImpl() = default;
OSCryptImpl::~OSCryptImpl() = default;
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->clear();
return true;
}
base::span<const uint8_t> key;
if (DeriveV11Key()) {
key = *v11_key_;
*ciphertext = kObfuscationPrefixV11;
} else {
key = kV10Key;
*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->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.
base::span<const uint8_t> key;
std::string obfuscation_prefix;
os_crypt::EncryptionPrefixVersion encryption_version =
os_crypt::EncryptionPrefixVersion::kNoVersion;
if (base::StartsWith(ciphertext, kObfuscationPrefixV10,
base::CompareCase::SENSITIVE)) {
key = kV10Key;
obfuscation_prefix = kObfuscationPrefixV10;
encryption_version = os_crypt::EncryptionPrefixVersion::kVersion10;
} else if (base::StartsWith(ciphertext, kObfuscationPrefixV11,
base::CompareCase::SENSITIVE)) {
if (!DeriveV11Key()) {
VLOG(1) << "Decryption failed: could not get the key";
return false;
}
key = *v11_key_;
obfuscation_prefix = kObfuscationPrefixV11;
encryption_version = os_crypt::EncryptionPrefixVersion::kVersion11;
}
os_crypt::LogEncryptionVersion(encryption_version);
if (encryption_version == os_crypt::EncryptionPrefixVersion::kNoVersion) {
return false;
}
// Strip off the versioning prefix before decrypting.
const std::string raw_ciphertext =
ciphertext.substr(obfuscation_prefix.length());
std::optional<std::vector<uint8_t>> maybe_plain =
crypto::aes_cbc::Decrypt(key, kIv, base::as_byte_span(raw_ciphertext));
if (maybe_plain) {
base::UmaHistogramBoolean(kMetricDecryptedWithEmptyKey, false);
plaintext->assign(base::as_string_view(*maybe_plain));
return true;
}
// Decryption failed - try the empty fallback key. See
// https://crbug.com/40055416.
maybe_plain = crypto::aes_cbc::Decrypt(kEmptyKey, kIv,
base::as_byte_span(raw_ciphertext));
if (maybe_plain) {
VLOG(1) << "Decryption succeeded after retrying with an empty key";
base::UmaHistogramBoolean(kMetricDecryptedWithEmptyKey, true);
plaintext->assign(base::as_string_view(*maybe_plain));
return true;
}
VLOG(1) << "Decryption failed";
base::UmaHistogramBoolean(kMetricDecryptedWithEmptyKey, false);
return false;
}
void OSCryptImpl::SetConfig(std::unique_ptr<os_crypt::Config> config) {
CHECK(!v11_key_);
config_ = std::move(config);
}
bool OSCryptImpl::IsEncryptionAvailable() {
// IsEncryptionAvailable() actually means "is real encryption backed by the
// system secret store available", which here means a v11 key is available,
// as opposed to the hardcoded v10 obfuscation key. Therefore, try deriving a
// v11 key - if one is already available this function will just return true.
return DeriveV11Key();
}
void OSCryptImpl::SetRawEncryptionKey(const std::string& raw_key) {
base::AutoLock auto_lock(OSCryptImpl::GetLock());
// Check if the v11 password is already cached. If it is, then data encrypted
// with the old password might not be decryptable.
CHECK(!v11_key_);
// The config won't be used if this function is being called. Callers should
// choose between setting a config and setting a raw encryption key.
CHECK(!config_);
if (raw_key.empty()) {
// Empty key means a v11 key is not available on the browser side. To match
// the browser's behavior, this OSCryptImpl instance also should not try to
// derive a v11 key.
try_v11_ = false;
} else {
// If the provided key is non-empty, it's a derived key and can be stored
// directly. Also, set `try_v11_` regardless of its previous state since a
// v11 key is now available.
v11_key_.emplace(std::array<uint8_t, kDerivedKeyBytes>());
base::span(*v11_key_).copy_from(base::as_byte_span(raw_key));
try_v11_ = true;
}
}
std::string OSCryptImpl::GetRawEncryptionKey() {
return DeriveV11Key() ? std::string(base::as_string_view(*v11_key_))
: std::string();
}
void OSCryptImpl::ClearCacheForTesting() {
v11_key_ = std::nullopt;
try_v11_ = true;
config_.reset();
}
void OSCryptImpl::UseMockKeyStorageForTesting(
base::OnceCallback<std::unique_ptr<KeyStorageLinux>()>
storage_provider_factory) {
base::AutoLock auto_lock(OSCryptImpl::GetLock());
storage_provider_factory_for_testing_ = std::move(storage_provider_factory);
}
void OSCryptImpl::SetEncryptionPasswordForTesting(const std::string& password) {
ClearCacheForTesting(); // IN-TEST
v11_key_ = Pbkdf2(password);
try_v11_ = true;
}
crypto::SubtlePassKey OSCryptImpl::MakeCryptoPassKey() {
return crypto::SubtlePassKey{};
}
std::array<uint8_t, OSCryptImpl::kDerivedKeyBytes> OSCryptImpl::Pbkdf2(
const std::string& key) {
std::array<uint8_t, OSCryptImpl::kDerivedKeyBytes> result;
crypto::kdf::DeriveKeyPbkdf2HmacSha1(kParams, base::as_byte_span(key), kSalt,
result, MakeCryptoPassKey());
return result;
}
bool OSCryptImpl::DeriveV11Key() {
base::AutoLock auto_lock(OSCryptImpl::GetLock());
if (!try_v11_) {
return false;
}
if (v11_key_) {
return true;
}
std::unique_ptr<KeyStorageLinux> key_storage;
if (storage_provider_factory_for_testing_) {
key_storage = std::move(storage_provider_factory_for_testing_).Run();
} else {
if (config_) {
key_storage = KeyStorageLinux::CreateService(*config_);
config_.reset();
}
}
if (!key_storage) {
// No backend available, can't do v11.
try_v11_ = false;
return false;
}
std::optional<std::string> maybe_key = key_storage->GetKey();
if (maybe_key) {
v11_key_ = Pbkdf2(*maybe_key);
}
return v11_key_.has_value();
}
// static
base::Lock& OSCryptImpl::GetLock() {
static base::NoDestructor<base::Lock> os_crypt_lock;
return *os_crypt_lock;
}