blob: 346208d0bbf3e078a02b31f0760bf105e68790a6 [file] [log] [blame]
// 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/logging.h"
#include "components/gcm_driver/crypto/p256_key_util.h"
#include "components/leveldb_proto/proto_database_impl.h"
#include "crypto/random.h"
namespace gcm {
// Statistics are logged to UMA with this string as part of histogram name. They
// can all be found under LevelDB.*.GCMKeyStore. Changing this needs to
// synchronize with histograms.xml, AND will also become incompatible with older
// browsers still reporting the previous values.
const char kDatabaseUMAClientName[] = "GCMKeyStore";
// 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;
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),
weak_factory_(this) {
DCHECK(blocking_task_runner);
}
GCMKeyStore::~GCMKeyStore() {}
void GCMKeyStore::GetKeys(const std::string& app_id,
const KeysCallback& callback) {
LazyInitialize(base::Bind(&GCMKeyStore::GetKeysAfterInitialize,
weak_factory_.GetWeakPtr(), app_id, callback));
}
void GCMKeyStore::GetKeysAfterInitialize(const std::string& app_id,
const KeysCallback& callback) {
DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED);
const auto& iter = key_pairs_.find(app_id);
if (iter == key_pairs_.end() || state_ != State::INITIALIZED) {
callback.Run(KeyPair(), std::string() /* auth_secret */);
return;
}
const auto& auth_secret_iter = auth_secrets_.find(app_id);
DCHECK(auth_secret_iter != auth_secrets_.end());
callback.Run(iter->second, auth_secret_iter->second);
}
void GCMKeyStore::CreateKeys(const std::string& app_id,
const KeysCallback& callback) {
LazyInitialize(base::Bind(&GCMKeyStore::CreateKeysAfterInitialize,
weak_factory_.GetWeakPtr(), app_id, callback));
}
void GCMKeyStore::CreateKeysAfterInitialize(const std::string& app_id,
const KeysCallback& callback) {
DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED);
if (state_ != State::INITIALIZED) {
callback.Run(KeyPair(), std::string() /* auth_secret */);
return;
}
// Only allow creating new keys if no keys currently exist.
DCHECK_EQ(0u, key_pairs_.count(app_id));
std::string private_key, public_key_x509, public_key;
if (!CreateP256KeyPair(&private_key, &public_key_x509, &public_key)) {
NOTREACHED() << "Unable to initialize a P-256 key pair.";
callback.Run(KeyPair(), 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);
encryption_data.set_auth_secret(auth_secret);
KeyPair* pair = encryption_data.add_keys();
pair->set_type(KeyPair::ECDH_P256);
pair->set_private_key(private_key);
pair->set_public_key_x509(public_key_x509);
pair->set_public_key(public_key);
using EntryVectorType =
leveldb_proto::ProtoDatabase<EncryptionData>::KeyEntryVector;
scoped_ptr<EntryVectorType> entries_to_save(new EntryVectorType());
scoped_ptr<std::vector<std::string>> keys_to_remove(
new std::vector<std::string>());
entries_to_save->push_back(std::make_pair(app_id, encryption_data));
database_->UpdateEntries(
std::move(entries_to_save), std::move(keys_to_remove),
base::Bind(&GCMKeyStore::DidStoreKeys, weak_factory_.GetWeakPtr(), app_id,
*pair, auth_secret, callback));
}
void GCMKeyStore::DidStoreKeys(const std::string& app_id,
const KeyPair& pair,
const std::string& auth_secret,
const KeysCallback& callback,
bool success) {
DCHECK_EQ(0u, key_pairs_.count(app_id));
if (!success) {
DVLOG(1) << "Unable to store the created key in the GCM Key Store.";
callback.Run(KeyPair(), std::string() /* auth_secret */);
return;
}
key_pairs_[app_id] = pair;
auth_secrets_[app_id] = auth_secret;
callback.Run(key_pairs_[app_id], auth_secret);
}
void GCMKeyStore::DeleteKeys(const std::string& app_id,
const DeleteCallback& callback) {
LazyInitialize(base::Bind(&GCMKeyStore::DeleteKeysAfterInitialize,
weak_factory_.GetWeakPtr(), app_id, callback));
}
void GCMKeyStore::DeleteKeysAfterInitialize(const std::string& app_id,
const DeleteCallback& callback) {
DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED);
const auto iter = key_pairs_.find(app_id);
if (iter == key_pairs_.end() || state_ != State::INITIALIZED) {
callback.Run(true /* success */);
return;
}
using EntryVectorType =
leveldb_proto::ProtoDatabase<EncryptionData>::KeyEntryVector;
scoped_ptr<EntryVectorType> entries_to_save(new EntryVectorType());
scoped_ptr<std::vector<std::string>> keys_to_remove(
new std::vector<std::string>(1, app_id));
database_->UpdateEntries(
std::move(entries_to_save), std::move(keys_to_remove),
base::Bind(&GCMKeyStore::DidDeleteKeys, weak_factory_.GetWeakPtr(),
app_id, callback));
}
void GCMKeyStore::DidDeleteKeys(const std::string& app_id,
const DeleteCallback& callback,
bool success) {
if (!success) {
DVLOG(1) << "Unable to delete a key from the GCM Key Store.";
callback.Run(false /* success */);
return;
}
key_pairs_.erase(app_id);
auth_secrets_.erase(app_id);
callback.Run(true /* success */);
}
void GCMKeyStore::LazyInitialize(const base::Closure& done_closure) {
if (delayed_task_controller_.CanRunTaskWithoutDelay()) {
done_closure.Run();
return;
}
delayed_task_controller_.AddTask(done_closure);
if (state_ == State::INITIALIZING)
return;
state_ = State::INITIALIZING;
database_.reset(new leveldb_proto::ProtoDatabaseImpl<EncryptionData>(
blocking_task_runner_));
database_->Init(
kDatabaseUMAClientName, key_store_path_,
base::Bind(&GCMKeyStore::DidInitialize, weak_factory_.GetWeakPtr()));
}
void GCMKeyStore::DidInitialize(bool success) {
if (!success) {
DVLOG(1) << "Unable to initialize the GCM Key Store.";
state_ = State::FAILED;
delayed_task_controller_.SetReady();
return;
}
database_->LoadEntries(
base::Bind(&GCMKeyStore::DidLoadKeys, weak_factory_.GetWeakPtr()));
}
void GCMKeyStore::DidLoadKeys(bool success,
scoped_ptr<std::vector<EncryptionData>> entries) {
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) {
DCHECK_EQ(1, entry.keys_size());
key_pairs_[entry.app_id()] = entry.keys(0);
auth_secrets_[entry.app_id()] = entry.auth_secret();
}
state_ = State::INITIALIZED;
delayed_task_controller_.SetReady();
}
} // namespace gcm