From c77563c9f45fcfdddabfe1f14565ae920eab34f7 Mon Sep 17 00:00:00 2001 From: peter Date: Fri, 17 Jul 2015 05:04:15 -0700 Subject: [PATCH] Implement GCMKeyStore for storing public/private key-pairs. This CL implements a GCMKeyStore class, backed by a ProtoDatabase<>, that enables us to store public/private key-pairs for association with encrypted GCM messages. TBR=sdefresne (+2 lines in BUILD.gn) BUG=486040 Review URL: https://codereview.chromium.org/1226033002 Cr-Commit-Position: refs/heads/master@{#339242} --- components/BUILD.gn | 2 + components/components_tests.gyp | 5 + components/gcm_driver.gypi | 40 +++- components/gcm_driver/BUILD.gn | 2 - components/gcm_driver/crypto/BUILD.gn | 34 +++ components/gcm_driver/crypto/DEPS | 5 + components/gcm_driver/crypto/OWNERS | 1 + components/gcm_driver/crypto/gcm_key_store.cc | 207 ++++++++++++++++++ components/gcm_driver/crypto/gcm_key_store.h | 108 ++++++++++ .../gcm_driver/crypto/gcm_key_store_unittest.cc | 231 +++++++++++++++++++++ components/gcm_driver/crypto/proto/BUILD.gn | 14 ++ .../crypto/proto/gcm_encryption_data.proto | 43 ++++ 12 files changed, 682 insertions(+), 10 deletions(-) create mode 100644 components/gcm_driver/crypto/BUILD.gn create mode 100644 components/gcm_driver/crypto/DEPS create mode 100644 components/gcm_driver/crypto/OWNERS create mode 100644 components/gcm_driver/crypto/gcm_key_store.cc create mode 100644 components/gcm_driver/crypto/gcm_key_store.h create mode 100644 components/gcm_driver/crypto/gcm_key_store_unittest.cc create mode 100644 components/gcm_driver/crypto/proto/BUILD.gn create mode 100644 components/gcm_driver/crypto/proto/gcm_encryption_data.proto diff --git a/components/BUILD.gn b/components/BUILD.gn index 2d91f58850aa..443a5c17b2a6 100644 --- a/components/BUILD.gn +++ b/components/BUILD.gn @@ -46,6 +46,7 @@ group("all_components") { "//components/favicon_base", "//components/feedback", "//components/gcm_driver", + "//components/gcm_driver/crypto", "//components/gcm_driver/instance_id", "//components/google/core/browser", "//components/history/content/browser", @@ -286,6 +287,7 @@ test("components_unittests") { "//components/domain_reliability:unit_tests", "//components/favicon_base:unit_tests", "//components/gcm_driver:unit_tests", + "//components/gcm_driver/crypto:unit_tests", "//components/gcm_driver/instance_id:unit_tests", "//components/google/core/browser:unit_tests", "//components/invalidation/impl:unittests", diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 4fef37627bd5..2e7bb3c07c88 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -230,6 +230,9 @@ 'gcm_driver/gcm_driver_desktop_unittest.cc', 'gcm_driver/gcm_stats_recorder_impl_unittest.cc', ], + 'gcm_driver_crypto_unittest_sources': [ + 'gcm_driver/crypto/gcm_key_store_unittest.cc', + ], 'google_unittest_sources': [ 'google/core/browser/google_url_tracker_unittest.cc', 'google/core/browser/google_util_unittest.cc', @@ -718,6 +721,7 @@ '<@(favicon_base_unittest_sources)', '<@(favicon_unittest_sources)', '<@(gcm_driver_unittest_sources)', + '<@(gcm_driver_crypto_unittest_sources)', '<@(google_unittest_sources)', '<@(history_unittest_sources)', '<@(instance_id_unittest_sources)', @@ -814,6 +818,7 @@ 'components.gyp:favicon_base', 'components.gyp:favicon_core', 'components.gyp:gcm_driver', + 'components.gyp:gcm_driver_crypto', 'components.gyp:gcm_driver_test_support', 'components.gyp:google_core_browser', 'components.gyp:history_core_browser', diff --git a/components/gcm_driver.gypi b/components/gcm_driver.gypi index bb9a64dfd558..c927e9ad3614 100644 --- a/components/gcm_driver.gypi +++ b/components/gcm_driver.gypi @@ -77,11 +77,6 @@ 'gcm_driver/system_encryptor.cc', 'gcm_driver/system_encryptor.h', ], - 'variables': { - 'proto_in_dir': 'gcm_driver/proto', - 'proto_out_dir': 'components/gcm_driver/proto', - }, - 'includes': [ '../build/protoc.gypi' ], 'conditions': [ ['OS == "android"', { 'dependencies': [ @@ -101,13 +96,10 @@ 'gcm_driver/gcm_client_factory.h', 'gcm_driver/gcm_client_impl.cc', 'gcm_driver/gcm_client_impl.h', - 'gcm_driver/gcm_delayed_task_controller.cc', - 'gcm_driver/gcm_delayed_task_controller.h', 'gcm_driver/gcm_driver_desktop.cc', 'gcm_driver/gcm_driver_desktop.h', 'gcm_driver/gcm_stats_recorder_impl.cc', 'gcm_driver/gcm_stats_recorder_impl.h', - 'gcm_driver/proto/gcm_channel_status.proto', ], }], ['chromeos == 1', { @@ -200,6 +192,38 @@ 'gcm_driver/instance_id/fake_gcm_driver_for_instance_id.h', ], }, + { + # GN version: //components/gcm_driver/crypto + 'target_name': 'gcm_driver_crypto', + 'type': 'static_library', + 'dependencies': [ + 'gcm_driver_crypto_proto', + '../base/base.gyp:base', + '../components/components.gyp:leveldb_proto', + '../crypto/crypto.gyp:crypto', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + # Note: file list duplicated in GN build. + 'gcm_driver/crypto/gcm_key_store.cc', + 'gcm_driver/crypto/gcm_key_store.h', + ], + }, + { + # GN version: //components/gcm_driver/crypto/proto + 'target_name': 'gcm_driver_crypto_proto', + 'type': 'static_library', + 'sources': [ + 'gcm_driver/crypto/proto/gcm_encryption_data.proto', + ], + 'variables': { + 'proto_in_dir': 'gcm_driver/crypto/proto', + 'proto_out_dir': 'components/gcm_driver/crypto/proto', + }, + 'includes': [ '../build/protoc.gypi' ], + }, ], 'conditions': [ ['OS == "android"', { diff --git a/components/gcm_driver/BUILD.gn b/components/gcm_driver/BUILD.gn index 58f9c1b33f42..6af5076dfcaa 100644 --- a/components/gcm_driver/BUILD.gn +++ b/components/gcm_driver/BUILD.gn @@ -81,8 +81,6 @@ static_library("gcm_driver") { "gcm_client_factory.h", "gcm_client_impl.cc", "gcm_client_impl.h", - "gcm_delayed_task_controller.cc", - "gcm_delayed_task_controller.h", "gcm_driver_desktop.cc", "gcm_driver_desktop.h", "gcm_stats_recorder_impl.cc", diff --git a/components/gcm_driver/crypto/BUILD.gn b/components/gcm_driver/crypto/BUILD.gn new file mode 100644 index 000000000000..d3aef296fdef --- /dev/null +++ b/components/gcm_driver/crypto/BUILD.gn @@ -0,0 +1,34 @@ +# 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. + +# GYP version: components/gcm_driver.gypi:gcm_driver_crypto +source_set("crypto") { + sources = [ + "gcm_key_store.cc", + "gcm_key_store.h", + ] + + deps = [ + "//base", + "//crypto", + "//components/gcm_driver", + "//components/gcm_driver/crypto/proto", + "//components/leveldb_proto", + "//third_party/protobuf:protobuf_lite", + ] +} + +source_set("unit_tests") { + testonly = true + sources = [ + "gcm_key_store_unittest.cc", + ] + + deps = [ + ":crypto", + "//base", + "//testing/gtest", + "//third_party/protobuf:protobuf_lite", + ] +} diff --git a/components/gcm_driver/crypto/DEPS b/components/gcm_driver/crypto/DEPS new file mode 100644 index 000000000000..ebda3fe009fa --- /dev/null +++ b/components/gcm_driver/crypto/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+components/gcm_driver", + "+components/leveldb_proto", + "+crypto", +] diff --git a/components/gcm_driver/crypto/OWNERS b/components/gcm_driver/crypto/OWNERS new file mode 100644 index 000000000000..2fca67fdfdae --- /dev/null +++ b/components/gcm_driver/crypto/OWNERS @@ -0,0 +1 @@ +peter@chromium.org \ No newline at end of file diff --git a/components/gcm_driver/crypto/gcm_key_store.cc b/components/gcm_driver/crypto/gcm_key_store.cc new file mode 100644 index 000000000000..d445efe383aa --- /dev/null +++ b/components/gcm_driver/crypto/gcm_key_store.cc @@ -0,0 +1,207 @@ +// 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 + +#include "base/logging.h" +#include "components/leveldb_proto/proto_database_impl.h" +#include "crypto/curve25519.h" +#include "crypto/random.h" + +namespace gcm { + +enum class GCMKeyStore::State { + UNINITIALIZED, + INITIALIZING, + INITIALIZED, + FAILED +}; + +GCMKeyStore::GCMKeyStore( + const base::FilePath& key_store_path, + scoped_refptr background_task_runner) + : key_store_path_(key_store_path), + database_(new leveldb_proto::ProtoDatabaseImpl( + background_task_runner)), + state_(State::UNINITIALIZED) { +} + +GCMKeyStore::~GCMKeyStore() {} + +void GCMKeyStore::GetKeys(const std::string& app_id, + const KeysCallback& callback) { + LazyInitialize(base::Bind(&GCMKeyStore::GetKeysAfterInitialize, this, 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()); + return; + } + + callback.Run(iter->second); +} + +void GCMKeyStore::CreateKeys(const std::string& app_id, + const KeysCallback& callback) { + LazyInitialize(base::Bind(&GCMKeyStore::CreateKeysAfterInitialize, this, + 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()); + return; + } + + // Only allow creating new keys if no keys currently exist. + DCHECK_EQ(0u, key_pairs_.count(app_id)); + + // Create a Curve25519 private/public key-pair. + uint8_t private_key[crypto::curve25519::kScalarBytes]; + uint8_t public_key[crypto::curve25519::kBytes]; + + crypto::RandBytes(private_key, sizeof(private_key)); + + // Compute the |public_key| based on the |private_key|. + crypto::curve25519::ScalarBaseMult(private_key, public_key); + + // Store the keys in a new EncryptionData object. + EncryptionData encryption_data; + encryption_data.set_app_id(app_id); + + KeyPair* pair = encryption_data.add_keys(); + pair->set_type(KeyPair::ECDH_CURVE_25519); + pair->set_private_key(private_key, sizeof(private_key)); + pair->set_public_key(public_key, sizeof(public_key)); + + using EntryVectorType = + leveldb_proto::ProtoDatabase::KeyEntryVector; + + scoped_ptr entries_to_save(new EntryVectorType()); + scoped_ptr> keys_to_remove( + new std::vector()); + + entries_to_save->push_back(std::make_pair(app_id, encryption_data)); + + database_->UpdateEntries(entries_to_save.Pass(), keys_to_remove.Pass(), + base::Bind(&GCMKeyStore::DidStoreKeys, this, app_id, + *pair, callback)); +} + +void GCMKeyStore::DidStoreKeys(const std::string& app_id, + const KeyPair& pair, + 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()); + return; + } + + key_pairs_[app_id] = pair; + + callback.Run(key_pairs_[app_id]); +} + +void GCMKeyStore::DeleteKeys(const std::string& app_id, + const DeleteCallback& callback) { + LazyInitialize(base::Bind(&GCMKeyStore::DeleteKeysAfterInitialize, this, + 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::KeyEntryVector; + + scoped_ptr entries_to_save(new EntryVectorType()); + scoped_ptr> keys_to_remove( + new std::vector(1, app_id)); + + database_->UpdateEntries( + entries_to_save.Pass(), keys_to_remove.Pass(), + base::Bind(&GCMKeyStore::DidDeleteKeys, this, 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); + + 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_->Init(key_store_path_, + base::Bind(&GCMKeyStore::DidInitialize, this)); +} + +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, this)); +} + +void GCMKeyStore::DidLoadKeys(bool success, + scoped_ptr> 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); + } + + state_ = State::INITIALIZED; + + delayed_task_controller_.SetReady(); +} + +} // namespace gcm diff --git a/components/gcm_driver/crypto/gcm_key_store.h b/components/gcm_driver/crypto/gcm_key_store.h new file mode 100644 index 000000000000..c35bb310f4bb --- /dev/null +++ b/components/gcm_driver/crypto/gcm_key_store.h @@ -0,0 +1,108 @@ +// 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. + +#ifndef COMPONENTS_GCM_DRIVER_CRYPTO_GCM_KEY_STORE_H_ +#define COMPONENTS_GCM_DRIVER_CRYPTO_GCM_KEY_STORE_H_ + +#include + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "components/gcm_driver/crypto/proto/gcm_encryption_data.pb.h" +#include "components/gcm_driver/gcm_delayed_task_controller.h" +#include "components/leveldb_proto/proto_database.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace gcm { + +// Key storage for use with encrypted messages received from Google Cloud +// Messaging. It provides the ability to create and store a key-pair for a given +// app id, as well as retrieving and deleting key-pairs. +// +// This class is backed by a proto database and might end up doing file I/O on +// a background task runner. For this reason, all public APIs take a callback +// rather than returning the result. Do not rely on the timing of the callbacks. +// +// Started operations will be completed even after the reference to the store +// has been freed (asynchronous operations increase reference count to |this|). +class GCMKeyStore : public base::RefCounted { + public: + using KeysCallback = base::Callback; + using DeleteCallback = base::Callback; + + GCMKeyStore(const base::FilePath& key_store_path, + scoped_refptr background_task_runner); + + // Retrieves the public/private key-pair associated with |app_id|, and + // invokes |callback| when they are available, or when an error occurred. + void GetKeys(const std::string& app_id, const KeysCallback& callback); + + // Creates a new public/private key-pair for |app_id|, and invokes + // |callback| when they are available, or when an error occurred. + void CreateKeys(const std::string& app_id, const KeysCallback& callback); + + // Deletes the keys associated with |app_id|, and invokes |callback| when + // the deletion has finished, or when an error occurred. + void DeleteKeys(const std::string& app_id, const DeleteCallback& callback); + + private: + friend class base::RefCounted; + + virtual ~GCMKeyStore(); + + // Initializes the database if necessary, and runs |done_closure| when done. + void LazyInitialize(const base::Closure& done_closure); + + void DidInitialize(bool success); + void DidLoadKeys(bool success, + scoped_ptr> entries); + + void DidStoreKeys(const std::string& app_id, + const KeyPair& pair, + const KeysCallback& callback, + bool success); + + void DidDeleteKeys(const std::string& app_id, + const DeleteCallback& callback, + bool success); + + // Private implementations of the API that will be executed when the database + // has either been successfully loaded, or failed to load. + + void GetKeysAfterInitialize(const std::string& app_id, + const KeysCallback& callback); + void CreateKeysAfterInitialize(const std::string& app_id, + const KeysCallback& callback); + void DeleteKeysAfterInitialize(const std::string& app_id, + const DeleteCallback& callback); + + // Path in which the key store database will be saved. + base::FilePath key_store_path_; + + // Instance of the ProtoDatabase backing the key store. + scoped_ptr> database_; + + enum class State; + + // The current state of the database. It has to be initialized before use. + State state_; + + // Controller for tasks that should be executed once the key store has + // finished initializing. + GCMDelayedTaskController delayed_task_controller_; + + // Mapping of an app id to the loaded EncryptedData structure. + std::map key_pairs_; + + DISALLOW_COPY_AND_ASSIGN(GCMKeyStore); +}; + +} // namespace gcm + +#endif // COMPONENTS_GCM_DRIVER_CRYPTO_GCM_KEY_STORE_H_ diff --git a/components/gcm_driver/crypto/gcm_key_store_unittest.cc b/components/gcm_driver/crypto/gcm_key_store_unittest.cc new file mode 100644 index 000000000000..0b063972969b --- /dev/null +++ b/components/gcm_driver/crypto/gcm_key_store_unittest.cc @@ -0,0 +1,231 @@ +// 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 "base/bind.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/thread_task_runner_handle.h" +#include "crypto/curve25519.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gcm { + +namespace { + +const char kFakeAppId[] = "my_app_id"; +const char kSecondFakeAppId[] = "my_other_app_id"; + +class GCMKeyStoreTest : public ::testing::Test { + public: + GCMKeyStoreTest() {} + ~GCMKeyStoreTest() override {} + + void SetUp() override { + ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); + CreateKeyStore(); + } + + void TearDown() override { + gcm_key_store_ = nullptr; + + // |gcm_key_store_| owns a ProtoDatabaseImpl whose destructor deletes the + // underlying LevelDB database on the task runner. + base::RunLoop().RunUntilIdle(); + } + + // Creates the GCM Key Store instance. May be called from within a test's body + // to re-create the key store, causing the database to re-open. + void CreateKeyStore() { + gcm_key_store_ = new GCMKeyStore(scoped_temp_dir_.path(), + message_loop_.task_runner()); + } + + // Callback to use with GCMKeyStore::{GetKeys, CreateKeys} calls. + void GotKeys(KeyPair* pair_out, const KeyPair& pair) { + DCHECK(pair_out); + + *pair_out = pair; + } + + // Callback to use with GCMKeyStore::DeleteKeys calls. + void DeletedKeys(bool* success_out, bool success) { + DCHECK(success_out); + + *success_out = success; + } + + protected: + GCMKeyStore* gcm_key_store() { return gcm_key_store_.get(); } + + private: + base::MessageLoop message_loop_; + base::ScopedTempDir scoped_temp_dir_; + + scoped_refptr gcm_key_store_; +}; + +TEST_F(GCMKeyStoreTest, EmptyByDefault) { + KeyPair pair; + gcm_key_store()->GetKeys(kFakeAppId, + base::Bind(&GCMKeyStoreTest::GotKeys, + base::Unretained(this), &pair)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_FALSE(pair.IsInitialized()); + EXPECT_FALSE(pair.has_type()); +} + +TEST_F(GCMKeyStoreTest, CreateAndGetKeys) { + KeyPair pair; + gcm_key_store()->CreateKeys(kFakeAppId, + base::Bind(&GCMKeyStoreTest::GotKeys, + base::Unretained(this), &pair)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(pair.IsInitialized()); + + ASSERT_TRUE(pair.has_private_key()); + EXPECT_EQ(crypto::curve25519::kScalarBytes, pair.private_key().size()); + + ASSERT_TRUE(pair.has_public_key()); + EXPECT_EQ(crypto::curve25519::kBytes, pair.public_key().size()); + + KeyPair read_pair; + gcm_key_store()->GetKeys(kFakeAppId, + base::Bind(&GCMKeyStoreTest::GotKeys, + base::Unretained(this), &read_pair)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(read_pair.IsInitialized()); + + EXPECT_EQ(pair.type(), read_pair.type()); + EXPECT_EQ(pair.private_key(), read_pair.private_key()); + EXPECT_EQ(pair.public_key(), read_pair.public_key()); +} + +TEST_F(GCMKeyStoreTest, KeysPersistenceBetweenInstances) { + KeyPair pair; + gcm_key_store()->CreateKeys(kFakeAppId, + base::Bind(&GCMKeyStoreTest::GotKeys, + base::Unretained(this), &pair)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(pair.IsInitialized()); + + // Create a new GCM Key Store instance. + CreateKeyStore(); + + KeyPair read_pair; + gcm_key_store()->GetKeys(kFakeAppId, + base::Bind(&GCMKeyStoreTest::GotKeys, + base::Unretained(this), &read_pair)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(read_pair.IsInitialized()); + EXPECT_TRUE(read_pair.has_type()); +} + +TEST_F(GCMKeyStoreTest, CreateAndDeleteKeys) { + KeyPair pair; + gcm_key_store()->CreateKeys(kFakeAppId, + base::Bind(&GCMKeyStoreTest::GotKeys, + base::Unretained(this), &pair)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(pair.IsInitialized()); + + KeyPair read_pair; + gcm_key_store()->GetKeys(kFakeAppId, + base::Bind(&GCMKeyStoreTest::GotKeys, + base::Unretained(this), &read_pair)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(read_pair.IsInitialized()); + EXPECT_TRUE(read_pair.has_type()); + + bool success = false; + gcm_key_store()->DeleteKeys(kFakeAppId, + base::Bind(&GCMKeyStoreTest::DeletedKeys, + base::Unretained(this), &success)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(success); + + gcm_key_store()->GetKeys(kFakeAppId, + base::Bind(&GCMKeyStoreTest::GotKeys, + base::Unretained(this), &read_pair)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_FALSE(read_pair.IsInitialized()); +} + +TEST_F(GCMKeyStoreTest, GetKeysMultipleAppIds) { + KeyPair pair; + gcm_key_store()->CreateKeys(kFakeAppId, + base::Bind(&GCMKeyStoreTest::GotKeys, + base::Unretained(this), &pair)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(pair.IsInitialized()); + + gcm_key_store()->CreateKeys(kSecondFakeAppId, + base::Bind(&GCMKeyStoreTest::GotKeys, + base::Unretained(this), &pair)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(pair.IsInitialized()); + + KeyPair read_pair; + gcm_key_store()->GetKeys(kFakeAppId, + base::Bind(&GCMKeyStoreTest::GotKeys, + base::Unretained(this), &read_pair)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(read_pair.IsInitialized()); + EXPECT_TRUE(read_pair.has_type()); +} + +TEST_F(GCMKeyStoreTest, SuccessiveCallsBeforeInitialization) { + KeyPair pair; + gcm_key_store()->CreateKeys(kFakeAppId, + base::Bind(&GCMKeyStoreTest::GotKeys, + base::Unretained(this), &pair)); + + // Deliberately do not run the message loop, so that the callback has not + // been resolved yet. The following EXPECT() ensures this. + EXPECT_FALSE(pair.IsInitialized()); + + KeyPair read_pair; + gcm_key_store()->GetKeys(kFakeAppId, + base::Bind(&GCMKeyStoreTest::GotKeys, + base::Unretained(this), &read_pair)); + + EXPECT_FALSE(read_pair.IsInitialized()); + + // Now run the message loop. Both tasks should have finished executing. Due + // to the asynchronous nature of operations, however, we can't rely on the + // write to have finished before the read begins. + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(pair.IsInitialized()); +} + +} // namespace + +} // namespace gcm diff --git a/components/gcm_driver/crypto/proto/BUILD.gn b/components/gcm_driver/crypto/proto/BUILD.gn new file mode 100644 index 000000000000..852cebf8bc09 --- /dev/null +++ b/components/gcm_driver/crypto/proto/BUILD.gn @@ -0,0 +1,14 @@ +# 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. + +import("//third_party/protobuf/proto_library.gni") + +# GYP version: components/gcm_driver.gypi:gcm_driver_crypto_proto +proto_library("proto") { + visibility = [ "//components/gcm_driver/crypto" ] + + sources = [ + "gcm_encryption_data.proto", + ] +} diff --git a/components/gcm_driver/crypto/proto/gcm_encryption_data.proto b/components/gcm_driver/crypto/proto/gcm_encryption_data.proto new file mode 100644 index 000000000000..4c6ad49bbb7a --- /dev/null +++ b/components/gcm_driver/crypto/proto/gcm_encryption_data.proto @@ -0,0 +1,43 @@ +// 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. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package gcm; + +// Stores a public/private key-pair. +// Next tag: 3 +message KeyPair { + // The type of key used for key agreement. Currently only the ECDH key + // agreement scheme is supported, using Curve 25519. + enum KeyType { + ECDH_CURVE_25519 = 0; + } + + required KeyType type = 1; + + // The private key matching the size requirements of |type|. + required bytes private_key = 2; + + // The public key matching the size requirements of |type|. + required bytes public_key = 3; +} + +// Stores a vector of public/private key-pairs associated with an app id. +// +// In the current implementation, each app id will have a single encryption key- +// pair associated with it at most. The message allows for multiple key pairs +// in case we need to force-cycle all keys, allowing the old keys to remain +// valid for a period of time enabling the web app to update. +// +// Next tag: 2 +message EncryptionData { + // The app id to whom this encryption data belongs. + required string app_id = 1; + + // The actual public/private key-pairs. + repeated KeyPair keys = 2; +} -- 2.11.4.GIT