123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- // Copyright 2015 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/gcm_driver/crypto/gcm_key_store.h"
- #include <stddef.h>
- #include <utility>
- #include "base/bind.h"
- #include "base/callback.h"
- #include "base/callback_helpers.h"
- #include "base/logging.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/strings/string_util.h"
- #include "base/task/sequenced_task_runner.h"
- #include "components/gcm_driver/crypto/p256_key_util.h"
- #include "components/leveldb_proto/public/proto_database_provider.h"
- #include "components/leveldb_proto/public/shared_proto_database_client_list.h"
- #include "crypto/random.h"
- #include "third_party/leveldatabase/env_chromium.h"
- namespace gcm {
- namespace {
- using EntryVectorType =
- leveldb_proto::ProtoDatabase<EncryptionData>::KeyEntryVector;
- // Number of cryptographically secure random bytes to generate as a key pair's
- // authentication secret. Must be at least 16 bytes.
- const size_t kAuthSecretBytes = 16;
- // Size cap for the leveldb log file before compression.
- const size_t kDatabaseWriteBufferSizeBytes = 16 * 1024;
- std::string DatabaseKey(const std::string& app_id,
- const std::string& authorized_entity) {
- DCHECK_EQ(std::string::npos, app_id.find(','));
- DCHECK_EQ(std::string::npos, authorized_entity.find(','));
- DCHECK_NE("*", authorized_entity) << "Wildcards require special handling";
- return authorized_entity.empty()
- ? app_id // No comma, for compatibility with existing keys.
- : app_id + ',' + authorized_entity;
- }
- leveldb_env::Options CreateLevelDbOptions() {
- leveldb_env::Options options;
- options.create_if_missing = true;
- options.max_open_files = 0; // Use minimum.
- options.write_buffer_size = kDatabaseWriteBufferSizeBytes;
- return options;
- }
- } // namespace
- enum class GCMKeyStore::State {
- UNINITIALIZED,
- INITIALIZING,
- INITIALIZED,
- FAILED
- };
- GCMKeyStore::GCMKeyStore(
- const base::FilePath& key_store_path,
- const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner)
- : key_store_path_(key_store_path),
- blocking_task_runner_(blocking_task_runner),
- state_(State::UNINITIALIZED) {
- DCHECK(blocking_task_runner);
- }
- GCMKeyStore::~GCMKeyStore() {}
- void GCMKeyStore::GetKeys(const std::string& app_id,
- const std::string& authorized_entity,
- bool fallback_to_empty_authorized_entity,
- KeysCallback callback) {
- LazyInitialize(
- base::BindOnce(&GCMKeyStore::GetKeysAfterInitialize,
- weak_factory_.GetWeakPtr(), app_id, authorized_entity,
- fallback_to_empty_authorized_entity, std::move(callback)));
- }
- void GCMKeyStore::GetKeysAfterInitialize(
- const std::string& app_id,
- const std::string& authorized_entity,
- bool fallback_to_empty_authorized_entity,
- KeysCallback callback) {
- DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED);
- bool success = false;
- if (state_ == State::INITIALIZED) {
- auto outer_iter = key_data_.find(app_id);
- if (outer_iter != key_data_.end()) {
- const auto& inner_map = outer_iter->second;
- auto inner_iter = inner_map.find(authorized_entity);
- if (fallback_to_empty_authorized_entity && inner_iter == inner_map.end())
- inner_iter = inner_map.find(std::string());
- if (inner_iter != inner_map.end()) {
- const auto& map_entry = inner_iter->second;
- std::move(callback).Run(map_entry.first->Copy(), map_entry.second);
- success = true;
- }
- }
- }
- UMA_HISTOGRAM_BOOLEAN("GCM.Crypto.GetKeySuccessRate", success);
- if (!success)
- std::move(callback).Run(nullptr /* key */, std::string() /* auth_secret */);
- }
- void GCMKeyStore::CreateKeys(const std::string& app_id,
- const std::string& authorized_entity,
- KeysCallback callback) {
- LazyInitialize(base::BindOnce(&GCMKeyStore::CreateKeysAfterInitialize,
- weak_factory_.GetWeakPtr(), app_id,
- authorized_entity, std::move(callback)));
- }
- void GCMKeyStore::CreateKeysAfterInitialize(
- const std::string& app_id,
- const std::string& authorized_entity,
- KeysCallback callback) {
- DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED);
- if (state_ != State::INITIALIZED) {
- std::move(callback).Run(nullptr /* key */, std::string() /* auth_secret */);
- return;
- }
- // Only allow creating new keys if no keys currently exist. Multiple Instance
- // ID tokens can share an app_id (with different authorized entities), but
- // InstanceID tokens can't share an app_id with a non-InstanceID registration.
- // This invariant is necessary for the fallback_to_empty_authorized_entity
- // mode of GetKey (needed by GCMEncryptionProvider::DecryptMessage, which
- // can't distinguish Instance ID tokens from non-InstanceID registrations).
- DCHECK(!key_data_.count(app_id) ||
- (!authorized_entity.empty() &&
- !key_data_[app_id].count(authorized_entity) &&
- !key_data_[app_id].count(std::string())))
- << "Instance ID tokens cannot share an app_id with a non-InstanceID GCM "
- "registration";
- std::unique_ptr<crypto::ECPrivateKey> key(crypto::ECPrivateKey::Create());
- if (!key) {
- NOTREACHED() << "Unable to initialize a P-256 key pair.";
- std::move(callback).Run(nullptr /* key */, std::string() /* auth_secret */);
- return;
- }
- std::string auth_secret;
- // Create the authentication secret, which has to be a cryptographically
- // secure random number of at least 128 bits (16 bytes).
- crypto::RandBytes(base::WriteInto(&auth_secret, kAuthSecretBytes + 1),
- kAuthSecretBytes);
- // Store the keys in a new EncryptionData object.
- EncryptionData encryption_data;
- encryption_data.set_app_id(app_id);
- if (!authorized_entity.empty())
- encryption_data.set_authorized_entity(authorized_entity);
- encryption_data.set_auth_secret(auth_secret);
- std::string private_key;
- bool success = GetRawPrivateKey(*key, &private_key);
- DCHECK(success);
- encryption_data.set_private_key(private_key);
- // Write them immediately to our cache, so subsequent calls to
- // {Get/Create/Remove}Keys can see them.
- key_data_[app_id][authorized_entity] = {key->Copy(), auth_secret};
- std::unique_ptr<EntryVectorType> entries_to_save(new EntryVectorType());
- std::unique_ptr<std::vector<std::string>> keys_to_remove(
- new std::vector<std::string>());
- entries_to_save->push_back(
- std::make_pair(DatabaseKey(app_id, authorized_entity), encryption_data));
- database_->UpdateEntries(
- std::move(entries_to_save), std::move(keys_to_remove),
- base::BindOnce(&GCMKeyStore::DidStoreKeys, weak_factory_.GetWeakPtr(),
- std::move(key), auth_secret, std::move(callback)));
- }
- void GCMKeyStore::DidStoreKeys(std::unique_ptr<crypto::ECPrivateKey> pair,
- const std::string& auth_secret,
- KeysCallback callback,
- bool success) {
- UMA_HISTOGRAM_BOOLEAN("GCM.Crypto.CreateKeySuccessRate", success);
- if (!success) {
- DVLOG(1) << "Unable to store the created key in the GCM Key Store.";
- // Our cache is now inconsistent. Reject requests until restarted.
- state_ = State::FAILED;
- std::move(callback).Run(nullptr /* key */, std::string() /* auth_secret */);
- return;
- }
- std::move(callback).Run(std::move(pair), auth_secret);
- }
- void GCMKeyStore::RemoveKeys(const std::string& app_id,
- const std::string& authorized_entity,
- base::OnceClosure callback) {
- LazyInitialize(base::BindOnce(&GCMKeyStore::RemoveKeysAfterInitialize,
- weak_factory_.GetWeakPtr(), app_id,
- authorized_entity, std::move(callback)));
- }
- void GCMKeyStore::RemoveKeysAfterInitialize(
- const std::string& app_id,
- const std::string& authorized_entity,
- base::OnceClosure callback) {
- DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED);
- const auto& outer_iter = key_data_.find(app_id);
- if (outer_iter == key_data_.end() || state_ != State::INITIALIZED) {
- std::move(callback).Run();
- return;
- }
- std::unique_ptr<EntryVectorType> entries_to_save(new EntryVectorType());
- std::unique_ptr<std::vector<std::string>> keys_to_remove(
- new std::vector<std::string>());
- bool had_keys = false;
- auto& inner_map = outer_iter->second;
- for (auto it = inner_map.begin(); it != inner_map.end();) {
- // Wildcard "*" matches all non-empty authorized entities (InstanceID only).
- if (authorized_entity == "*" ? !it->first.empty()
- : it->first == authorized_entity) {
- had_keys = true;
- keys_to_remove->push_back(DatabaseKey(app_id, it->first));
- // Clear keys immediately from our cache, so subsequent calls to
- // {Get/Create/Remove}Keys don't see them.
- it = inner_map.erase(it);
- } else {
- ++it;
- }
- }
- if (!had_keys) {
- std::move(callback).Run();
- return;
- }
- if (inner_map.empty())
- key_data_.erase(app_id);
- database_->UpdateEntries(
- std::move(entries_to_save), std::move(keys_to_remove),
- base::BindOnce(&GCMKeyStore::DidRemoveKeys, weak_factory_.GetWeakPtr(),
- std::move(callback)));
- }
- void GCMKeyStore::DidRemoveKeys(base::OnceClosure callback, bool success) {
- UMA_HISTOGRAM_BOOLEAN("GCM.Crypto.RemoveKeySuccessRate", success);
- if (!success) {
- DVLOG(1) << "Unable to delete a key from the GCM Key Store.";
- // Our cache is now inconsistent. Reject requests until restarted.
- state_ = State::FAILED;
- }
- std::move(callback).Run();
- }
- void GCMKeyStore::DidUpgradeDatabase(bool success) {
- UMA_HISTOGRAM_BOOLEAN("GCM.Crypto.GCMDatabaseUpgradeResult", success);
- if (!success) {
- DVLOG(1) << "Unable to upgrade the GCM Key Store database.";
- // Our cache is now inconsistent. Reject requests until restarted.
- state_ = State::FAILED;
- delayed_task_controller_.SetReady();
- return;
- }
- database_->LoadEntries(
- base::BindOnce(&GCMKeyStore::DidLoadKeys, weak_factory_.GetWeakPtr()));
- }
- void GCMKeyStore::LazyInitialize(base::OnceClosure done_closure) {
- if (delayed_task_controller_.CanRunTaskWithoutDelay()) {
- std::move(done_closure).Run();
- return;
- }
- delayed_task_controller_.AddTask(std::move(done_closure));
- if (state_ == State::INITIALIZING)
- return;
- state_ = State::INITIALIZING;
- database_ = leveldb_proto::ProtoDatabaseProvider::GetUniqueDB<EncryptionData>(
- leveldb_proto::ProtoDbType::GCM_KEY_STORE, key_store_path_,
- blocking_task_runner_);
- database_->Init(
- CreateLevelDbOptions(),
- base::BindOnce(&GCMKeyStore::DidInitialize, weak_factory_.GetWeakPtr()));
- }
- void GCMKeyStore::DidInitialize(leveldb_proto::Enums::InitStatus status) {
- bool success = status == leveldb_proto::Enums::kOK;
- UMA_HISTOGRAM_BOOLEAN("GCM.Crypto.InitKeyStoreSuccessRate", success);
- if (!success) {
- DVLOG(1) << "Unable to initialize the GCM Key Store.";
- state_ = State::FAILED;
- delayed_task_controller_.SetReady();
- return;
- }
- database_->LoadEntries(
- base::BindOnce(&GCMKeyStore::DidLoadKeys, weak_factory_.GetWeakPtr()));
- }
- void GCMKeyStore::UpgradeDatabase(
- std::unique_ptr<std::vector<EncryptionData>> entries) {
- std::unique_ptr<EntryVectorType> entries_to_save =
- std::make_unique<EntryVectorType>();
- std::unique_ptr<std::vector<std::string>> keys_to_remove =
- std::make_unique<std::vector<std::string>>();
- // Loop over entries, create list of database entries to overwrite.
- for (EncryptionData& entry : *entries) {
- if (!entry.keys_size())
- continue;
- std::string decrypted_private_key;
- if (!DecryptPrivateKey(entry.keys(0).private_key(),
- &decrypted_private_key)) {
- DVLOG(1) << "Unable to decrypt private key: "
- << entry.keys(0).private_key();
- state_ = State::FAILED;
- delayed_task_controller_.SetReady();
- UMA_HISTOGRAM_BOOLEAN("GCM.Crypto.GCMDatabaseUpgradeResult",
- false /* sucess */);
- return;
- }
- entry.set_private_key(decrypted_private_key);
- entry.clear_keys();
- entries_to_save->push_back(std::make_pair(
- DatabaseKey(entry.app_id(), entry.authorized_entity()), entry));
- }
- database_->UpdateEntries(std::move(entries_to_save),
- std::move(keys_to_remove),
- base::BindOnce(&GCMKeyStore::DidUpgradeDatabase,
- weak_factory_.GetWeakPtr()));
- }
- void GCMKeyStore::DidLoadKeys(
- bool success,
- std::unique_ptr<std::vector<EncryptionData>> entries) {
- UMA_HISTOGRAM_BOOLEAN("GCM.Crypto.LoadKeyStoreSuccessRate", success);
- if (!success) {
- DVLOG(1) << "Unable to load entries into the GCM Key Store.";
- state_ = State::FAILED;
- delayed_task_controller_.SetReady();
- return;
- }
- for (const EncryptionData& entry : *entries) {
- std::string authorized_entity;
- if (entry.has_authorized_entity())
- authorized_entity = entry.authorized_entity();
- std::unique_ptr<crypto::ECPrivateKey> key;
- // The old format of EncryptionData has a KeyPair in it. Previously
- // we used to cache the key pair and auth secret in key_data_.
- // The new code adds the pair {ECPrivateKey, auth_secret} to
- // key_data_ instead.
- if (entry.keys_size()) {
- if (state_ == State::FAILED)
- return;
- // Old format of EncryptionData. Upgrade database so there are no such
- // entries. We'll reload keys from the database once this is done.
- UpgradeDatabase(std::move(entries));
- return;
- } else {
- std::string private_key_str = entry.private_key();
- if (private_key_str.empty())
- continue;
- std::vector<uint8_t> private_key(private_key_str.begin(),
- private_key_str.end());
- key = crypto::ECPrivateKey::CreateFromPrivateKeyInfo(private_key);
- }
- key_data_[entry.app_id()][authorized_entity] =
- std::make_pair(std::move(key), entry.auth_secret());
- }
- state_ = State::INITIALIZED;
- delayed_task_controller_.SetReady();
- }
- bool GCMKeyStore::DecryptPrivateKey(const std::string& to_decrypt,
- std::string* decrypted) {
- DCHECK(decrypted);
- std::vector<uint8_t> to_decrypt_vector(to_decrypt.begin(), to_decrypt.end());
- std::unique_ptr<crypto::ECPrivateKey> key_to_decrypt =
- crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo(
- to_decrypt_vector);
- if (!key_to_decrypt)
- return false;
- std::vector<uint8_t> decrypted_vector;
- if (!key_to_decrypt->ExportPrivateKey(&decrypted_vector))
- return false;
- decrypted->assign(decrypted_vector.begin(), decrypted_vector.end());
- return true;
- }
- } // namespace gcm
|