summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpeter <peter@chromium.org>2015-07-17 05:04:15 -0700
committerCommit bot <commit-bot@chromium.org>2015-07-17 12:04:53 +0000
commitc77563c9f45fcfdddabfe1f14565ae920eab34f7 (patch)
tree909dd8102c118d43fdd70921284bdd2fa190e467
parent83011698be5f20098555b4d450aacf4fed1b7922 (diff)
downloadchromium_src-c77563c9f45fcfdddabfe1f14565ae920eab34f7.zip
chromium_src-c77563c9f45fcfdddabfe1f14565ae920eab34f7.tar.gz
chromium_src-c77563c9f45fcfdddabfe1f14565ae920eab34f7.tar.bz2
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}
-rw-r--r--components/BUILD.gn2
-rw-r--r--components/components_tests.gyp5
-rw-r--r--components/gcm_driver.gypi40
-rw-r--r--components/gcm_driver/BUILD.gn2
-rw-r--r--components/gcm_driver/crypto/BUILD.gn34
-rw-r--r--components/gcm_driver/crypto/DEPS5
-rw-r--r--components/gcm_driver/crypto/OWNERS1
-rw-r--r--components/gcm_driver/crypto/gcm_key_store.cc207
-rw-r--r--components/gcm_driver/crypto/gcm_key_store.h108
-rw-r--r--components/gcm_driver/crypto/gcm_key_store_unittest.cc231
-rw-r--r--components/gcm_driver/crypto/proto/BUILD.gn14
-rw-r--r--components/gcm_driver/crypto/proto/gcm_encryption_data.proto43
12 files changed, 682 insertions, 10 deletions
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 2d91f58..443a5c1 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 4fef376..2e7bb3c 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 bb9a64d..c927e9a 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 58f9c1b..6af5076 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 0000000..d3aef29
--- /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 0000000..ebda3fe
--- /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 0000000..2fca67f
--- /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 0000000..d445efe
--- /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 <utility>
+
+#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<base::SequencedTaskRunner> background_task_runner)
+ : key_store_path_(key_store_path),
+ database_(new leveldb_proto::ProtoDatabaseImpl<EncryptionData>(
+ 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<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(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<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(
+ 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<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);
+ }
+
+ 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 0000000..c35bb31
--- /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 <map>
+
+#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<GCMKeyStore> {
+ public:
+ using KeysCallback = base::Callback<void(const KeyPair& pair)>;
+ using DeleteCallback = base::Callback<void(bool success)>;
+
+ GCMKeyStore(const base::FilePath& key_store_path,
+ scoped_refptr<base::SequencedTaskRunner> 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<GCMKeyStore>;
+
+ 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<std::vector<EncryptionData>> 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<leveldb_proto::ProtoDatabase<EncryptionData>> 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<std::string, KeyPair> 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 0000000..0b06397
--- /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<GCMKeyStore> 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 0000000..852cebf
--- /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 0000000..4c6ad49
--- /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;
+}