summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/sync/protocol/encryption.proto27
-rw-r--r--chrome/browser/sync/protocol/nigori_specifics.proto35
-rwxr-xr-xchrome/browser/sync/protocol/sync_proto.gyp2
-rw-r--r--chrome/browser/sync/util/cryptographer.cc157
-rw-r--r--chrome/browser/sync/util/cryptographer.h107
-rw-r--r--chrome/browser/sync/util/cryptographer_unittest.cc134
-rw-r--r--chrome/browser/sync/util/nigori.cc3
-rw-r--r--chrome/browser/sync/util/nigori_unittest.cc8
8 files changed, 473 insertions, 0 deletions
diff --git a/chrome/browser/sync/protocol/encryption.proto b/chrome/browser/sync/protocol/encryption.proto
new file mode 100644
index 0000000..a115957
--- /dev/null
+++ b/chrome/browser/sync/protocol/encryption.proto
@@ -0,0 +1,27 @@
+// Copyright (c) 2010 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.
+//
+// Common sync protocol for encrypted data.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package sync_pb;
+
+// Encrypted sync data consists of two parts: a key name and a blob. Key name is
+// the name of the key that was used to encrypt blob and blob is encrypted data
+// itself.
+//
+// The reason we need to keep track of the key name is that a sync user can
+// change their passphrase (and thus their encryption key) at any time. When
+// that happens, we make a best effort to reencrypt all nodes with the new
+// passphrase, but since we don't have transactions on the server-side, we
+// cannot garantee that every node will be reencrypted. As a workaround, we keep
+// track of all keys, assign each key a name (by using that key to encrypt a
+// well known string) and keep track of which key was used to encrypt each node.
+message EncryptedData {
+ optional string key_name = 1;
+ optional string blob = 2;
+};
diff --git a/chrome/browser/sync/protocol/nigori_specifics.proto b/chrome/browser/sync/protocol/nigori_specifics.proto
new file mode 100644
index 0000000..386bb5a
--- /dev/null
+++ b/chrome/browser/sync/protocol/nigori_specifics.proto
@@ -0,0 +1,35 @@
+// Copyright (c) 2010 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.
+//
+// Sync protocol datatype extension for nigori keys.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package sync_pb;
+
+import "encryption.proto";
+import "sync.proto";
+
+message NigoriKey {
+ optional string name = 1;
+ optional string hostname = 2;
+ optional string username = 3;
+ optional string password = 4;
+}
+
+message NigoriKeyBag {
+ repeated NigoriKey key = 2;
+}
+
+// Properties of nigori sync object.
+message NigoriSpecifics {
+ optional EncryptedData encrypted = 1;
+}
+
+extend EntitySpecifics {
+ optional NigoriSpecifics nigori = 47745;
+}
+
diff --git a/chrome/browser/sync/protocol/sync_proto.gyp b/chrome/browser/sync/protocol/sync_proto.gyp
index c80e7d2..e4cde74 100755
--- a/chrome/browser/sync/protocol/sync_proto.gyp
+++ b/chrome/browser/sync/protocol/sync_proto.gyp
@@ -14,9 +14,11 @@
'type': 'none',
'sources': [
'sync.proto',
+ 'encryption.proto',
'autofill_specifics.proto',
'bookmark_specifics.proto',
'extension_specifics.proto',
+ 'nigori_specifics.proto',
'password_specifics.proto',
'preference_specifics.proto',
'theme_specifics.proto',
diff --git a/chrome/browser/sync/util/cryptographer.cc b/chrome/browser/sync/util/cryptographer.cc
new file mode 100644
index 0000000..0fbaa3a
--- /dev/null
+++ b/chrome/browser/sync/util/cryptographer.cc
@@ -0,0 +1,157 @@
+// Copyright (c) 2010 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 "chrome/browser/sync/util/cryptographer.h"
+
+namespace browser_sync {
+
+const char kNigoriTag[] = "nigori";
+
+// We name a particular Nigori instance (ie. a triplet consisting of a hostname,
+// a username, and a password) by calling Permute on this string. Since the
+// output of Permute is always the same for a given triplet, clients will always
+// assign the same name to a particular triplet.
+const char kNigoriKeyName[] = "nigori-key";
+
+Cryptographer::Cryptographer() {
+}
+
+bool Cryptographer::CanDecrypt(const sync_pb::EncryptedData& data) const {
+ return nigoris_.end() != nigoris_.find(data.key_name());
+}
+
+bool Cryptographer::Encrypt(const ::google::protobuf::MessageLite& message,
+ sync_pb::EncryptedData* encrypted) const {
+ DCHECK(encrypted);
+ DCHECK(default_nigori_);
+
+ std::string serialized;
+ if (!message.SerializeToString(&serialized)) {
+ NOTREACHED(); // |message| is invalid/missing a required field.
+ return false;
+ }
+
+ encrypted->set_key_name(default_nigori_->first);
+ if (!default_nigori_->second->Encrypt(serialized,
+ encrypted->mutable_blob())) {
+ NOTREACHED(); // Encrypt should not fail.
+ return false;
+ }
+ return true;
+}
+
+bool Cryptographer::Decrypt(const sync_pb::EncryptedData& encrypted,
+ ::google::protobuf::MessageLite* message) const {
+ DCHECK(message);
+
+ NigoriMap::const_iterator it = nigoris_.find(encrypted.key_name());
+ if (nigoris_.end() == it) {
+ NOTREACHED() << "Cannot decrypt message";
+ return false; // Caller should have called CanDecrypt(encrypt).
+ }
+
+ std::string plaintext;
+ if (!it->second->Decrypt(encrypted.blob(), &plaintext)) {
+ return false;
+ }
+
+ return message->ParseFromString(plaintext);
+}
+
+bool Cryptographer::GetKeys(sync_pb::EncryptedData* encrypted) const {
+ DCHECK(encrypted);
+ DCHECK(!nigoris_.empty());
+
+ // Create a bag of all the Nigori parameters we know about.
+ sync_pb::NigoriKeyBag bag;
+ for (NigoriMap::const_iterator it = nigoris_.begin(); it != nigoris_.end();
+ ++it) {
+ const Nigori& nigori = *it->second;
+ sync_pb::NigoriKey* key = bag.add_key();
+ key->set_name(it->first);
+ key->set_hostname(nigori.hostname());
+ key->set_username(nigori.username());
+ key->set_password(nigori.password());
+ }
+
+ // Encrypt the bag with the default Nigori.
+ return Encrypt(bag, encrypted);
+}
+
+bool Cryptographer::AddKey(const KeyParams& params) {
+ DCHECK(NULL == pending_keys_.get());
+
+ // Create the new Nigori and make it the default encryptor.
+ scoped_ptr<Nigori> nigori(new Nigori(params.hostname));
+ if (!nigori->Init(params.username, params.password)) {
+ NOTREACHED(); // Invalid username or password.
+ return false;
+ }
+ std::string name;
+ if (!nigori->Permute(Nigori::Password, kNigoriKeyName, &name)) {
+ NOTREACHED();
+ return false;
+ }
+ nigoris_[name] = make_linked_ptr(nigori.release());
+ default_nigori_ = &*nigoris_.find(name);
+ return true;
+}
+
+bool Cryptographer::SetKeys(const sync_pb::EncryptedData& encrypted) {
+ DCHECK(CanDecrypt(encrypted));
+
+ sync_pb::NigoriKeyBag bag;
+ if (!Decrypt(encrypted, &bag)) {
+ return false;
+ }
+ InstallKeys(encrypted.key_name(), bag);
+ return true;
+}
+
+void Cryptographer::SetPendingKeys(const sync_pb::EncryptedData& encrypted) {
+ DCHECK(!CanDecrypt(encrypted));
+ pending_keys_.reset(new sync_pb::EncryptedData(encrypted));
+}
+
+bool Cryptographer::DecryptPendingKeys(const KeyParams& params) {
+ Nigori nigori(params.hostname);
+ if (!nigori.Init(params.username, params.password)) {
+ NOTREACHED();
+ return false;
+ }
+
+ std::string plaintext;
+ if (!nigori.Decrypt(pending_keys_->blob(), &plaintext))
+ return false;
+
+ sync_pb::NigoriKeyBag bag;
+ if (!bag.ParseFromString(plaintext)) {
+ NOTREACHED();
+ return false;
+ }
+ InstallKeys(pending_keys_->key_name(), bag);
+ pending_keys_.reset();
+ return true;
+}
+
+void Cryptographer::InstallKeys(const std::string& default_key_name,
+ const sync_pb::NigoriKeyBag& bag) {
+ int key_size = bag.key_size();
+ for (int i = 0; i < key_size; ++i) {
+ const sync_pb::NigoriKey key = bag.key(i);
+ // Only use this key if we don't already know about it.
+ if (nigoris_.end() == nigoris_.find(key.name())) {
+ scoped_ptr<Nigori> new_nigori(new Nigori(key.hostname()));
+ if (!new_nigori->Init(key.username(), key.password())) {
+ NOTREACHED();
+ continue;
+ }
+ nigoris_[key.name()] = make_linked_ptr(new_nigori.release());
+ }
+ }
+ DCHECK(nigoris_.end() != nigoris_.find(default_key_name));
+ default_nigori_ = &*nigoris_.find(default_key_name);
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/util/cryptographer.h b/chrome/browser/sync/util/cryptographer.h
new file mode 100644
index 0000000..2d967fe
--- /dev/null
+++ b/chrome/browser/sync/util/cryptographer.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2010 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 CHROME_BROWSER_SYNC_UTIL_CRYPTOGRAPHER_H_
+#define CHROME_BROWSER_SYNC_UTIL_CRYPTOGRAPHER_H_
+
+#include <map>
+#include <string>
+
+#include "base/linked_ptr.h"
+#include "base/scoped_ptr.h"
+#include "chrome/browser/sync/protocol/nigori_specifics.pb.h"
+#include "chrome/browser/sync/util/nigori.h"
+
+namespace browser_sync {
+
+// The parameters used to initialize a Nigori instance.
+struct KeyParams {
+ std::string hostname;
+ std::string username;
+ std::string password;
+};
+
+// This class manages the Nigori objects used to encrypt and decrypt sensitive
+// sync data (eg. passwords). Each Nigori object knows how to handle data
+// protected with a particular passphrase.
+//
+// Whenever an update to the Nigori sync node is received from the server,
+// SetPendingKeys should be called with the encrypted contents of that node.
+// Most likely, an updated Nigori node means that a new passphrase has been set
+// and that future node updates won't be decryptable. To remedy this, the user
+// should be prompted for the new passphrase and DecryptPendingKeys be called.
+//
+// Whenever a update to an encrypted node is received from the server,
+// CanDecrypt should be used to verify whether the Cryptographer can decrypt
+// that node. If it cannot, then the application of that update should be
+// delayed until after it can be decrypted.
+class Cryptographer {
+ public:
+ Cryptographer();
+
+ // Returns whether we can decrypt |encrypted| using the keys we currently know
+ // about.
+ bool CanDecrypt(const sync_pb::EncryptedData& encrypted) const;
+
+ // Encrypts |message| into |encrypted|. Returns true unless encryption fails.
+ // Note that encryption will fail if |message| isn't valid (eg. a required
+ // field isn't set).
+ bool Encrypt(const ::google::protobuf::MessageLite& message,
+ sync_pb::EncryptedData* encrypted) const;
+
+ // Decrypts |encrypted| into |message|. Returns true unless decryption fails,
+ // or |message| fails to parse the decrypted data.
+ bool Decrypt(const sync_pb::EncryptedData& encrypted,
+ ::google::protobuf::MessageLite* message) const;
+
+ // Encrypts the set of currently known keys into |encrypted|. Returns true if
+ // successful.
+ bool GetKeys(sync_pb::EncryptedData* encrypted) const;
+
+ // Creates a new Nigori instance using |params|. If successful, |params| will
+ // become the default encryption key and be used for all future calls to
+ // Encrypt.
+ bool AddKey(const KeyParams& params);
+
+ // Decrypts |encrypted| and uses its contents to initialize Nigori instances.
+ // Returns true unless decryption of |encrypted| fails. The caller is
+ // responsible for checking that CanDecrypt(encrypted) == true.
+ bool SetKeys(const sync_pb::EncryptedData& encrypted);
+
+ // Makes a local copy of |encrypted| to later be decrypted by
+ // DecryptPendingKeys. This should only be used if CanDecrypt(encrypted) ==
+ // false.
+ void SetPendingKeys(const sync_pb::EncryptedData& encrypted);
+
+ // Attepmts to decrypt the set of keys that was copied in the previous call to
+ // SetPendingKeys using |params|. Returns true if the pending keys were
+ // successfully decrypted and installed.
+ bool DecryptPendingKeys(const KeyParams& params);
+
+ // Returns whether this Cryptographer is ready to encrypt and decrypt data.
+ bool is_ready() const { return !nigoris_.empty() && default_nigori_; }
+
+ // Returns whether there is a pending set of keys that needs to be decrypted.
+ bool has_pending_keys() const { return NULL != pending_keys_.get(); }
+
+ private:
+ typedef std::map<std::string, linked_ptr<const Nigori> > NigoriMap;
+
+ // Helper method to instancitate Nigori instances for each set of key
+ // parameters in |bag| and setting the default encryption key to
+ // |default_key_name|.
+ void InstallKeys(const std::string& default_key_name,
+ const sync_pb::NigoriKeyBag& bag);
+
+ NigoriMap nigoris_; // The Nigoris we know about, mapped by key name.
+ NigoriMap::value_type* default_nigori_; // The Nigori used for encryption.
+
+ scoped_ptr<sync_pb::EncryptedData> pending_keys_;
+
+ DISALLOW_COPY_AND_ASSIGN(Cryptographer);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_UTIL_CRYPTOGRAPHER_H_
diff --git a/chrome/browser/sync/util/cryptographer_unittest.cc b/chrome/browser/sync/util/cryptographer_unittest.cc
new file mode 100644
index 0000000..af5f0a2
--- /dev/null
+++ b/chrome/browser/sync/util/cryptographer_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2010 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 "chrome/browser/sync/util/cryptographer.h"
+
+#include <string>
+
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "chrome/browser/sync/protocol/password_specifics.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace browser_sync {
+namespace {
+
+TEST(CryptographerTest, EmptyCantDecrypt) {
+ Cryptographer cryptographer;
+ EXPECT_FALSE(cryptographer.is_ready());
+
+ sync_pb::EncryptedData encrypted;
+ encrypted.set_key_name("foo");
+ encrypted.set_blob("bar");
+
+ EXPECT_FALSE(cryptographer.CanDecrypt(encrypted));
+}
+
+TEST(CryptographerTest, MissingCantDecrypt) {
+ Cryptographer cryptographer;
+
+ KeyParams params = {"localhost", "dummy", "dummy"};
+ cryptographer.AddKey(params);
+ EXPECT_TRUE(cryptographer.is_ready());
+
+ sync_pb::EncryptedData encrypted;
+ encrypted.set_key_name("foo");
+ encrypted.set_blob("bar");
+
+ EXPECT_FALSE(cryptographer.CanDecrypt(encrypted));
+}
+
+TEST(CryptographerTest, CanEncryptAndDecrypt) {
+ Cryptographer cryptographer;
+
+ KeyParams params = {"localhost", "dummy", "dummy"};
+ EXPECT_TRUE(cryptographer.AddKey(params));
+ EXPECT_TRUE(cryptographer.is_ready());
+
+ sync_pb::PasswordSpecificsData original;
+ original.set_origin("http://example.com");
+ original.set_username_value("azure");
+ original.set_password_value("hunter2");
+
+ sync_pb::EncryptedData encrypted;
+ EXPECT_TRUE(cryptographer.Encrypt(original, &encrypted));
+
+ sync_pb::PasswordSpecificsData decrypted;
+ EXPECT_TRUE(cryptographer.Decrypt(encrypted, &decrypted));
+
+ EXPECT_EQ(original.SerializeAsString(), decrypted.SerializeAsString());
+}
+
+TEST(CryptographerTest, AddKeySetsDefault) {
+ Cryptographer cryptographer;
+
+ KeyParams params1 = {"localhost", "dummy", "dummy1"};
+ EXPECT_TRUE(cryptographer.AddKey(params1));
+ EXPECT_TRUE(cryptographer.is_ready());
+
+ sync_pb::PasswordSpecificsData original;
+ original.set_origin("http://example.com");
+ original.set_username_value("azure");
+ original.set_password_value("hunter2");
+
+ sync_pb::EncryptedData encrypted1;
+ EXPECT_TRUE(cryptographer.Encrypt(original, &encrypted1));
+ sync_pb::EncryptedData encrypted2;
+ EXPECT_TRUE(cryptographer.Encrypt(original, &encrypted2));
+
+ KeyParams params2 = {"localhost", "dummy", "dummy2"};
+ EXPECT_TRUE(cryptographer.AddKey(params2));
+ EXPECT_TRUE(cryptographer.is_ready());
+
+ sync_pb::EncryptedData encrypted3;
+ EXPECT_TRUE(cryptographer.Encrypt(original, &encrypted3));
+ sync_pb::EncryptedData encrypted4;
+ EXPECT_TRUE(cryptographer.Encrypt(original, &encrypted4));
+
+ EXPECT_EQ(encrypted1.key_name(), encrypted2.key_name());
+ EXPECT_NE(encrypted1.key_name(), encrypted3.key_name());
+ EXPECT_EQ(encrypted3.key_name(), encrypted4.key_name());
+}
+
+TEST(CryptographerTest, EncryptExportDecrypt) {
+ sync_pb::EncryptedData nigori;
+ sync_pb::EncryptedData encrypted;
+
+ sync_pb::PasswordSpecificsData original;
+ original.set_origin("http://example.com");
+ original.set_username_value("azure");
+ original.set_password_value("hunter2");
+
+ {
+ Cryptographer cryptographer;
+
+ KeyParams params = {"localhost", "dummy", "dummy"};
+ cryptographer.AddKey(params);
+ EXPECT_TRUE(cryptographer.is_ready());
+
+ EXPECT_TRUE(cryptographer.Encrypt(original, &encrypted));
+ EXPECT_TRUE(cryptographer.GetKeys(&nigori));
+ }
+
+ {
+ Cryptographer cryptographer;
+ EXPECT_FALSE(cryptographer.CanDecrypt(nigori));
+
+ cryptographer.SetPendingKeys(nigori);
+ EXPECT_FALSE(cryptographer.is_ready());
+ EXPECT_TRUE(cryptographer.has_pending_keys());
+
+ KeyParams params = {"localhost", "dummy", "dummy"};
+ EXPECT_TRUE(cryptographer.DecryptPendingKeys(params));
+ EXPECT_TRUE(cryptographer.is_ready());
+ EXPECT_FALSE(cryptographer.has_pending_keys());
+
+ sync_pb::PasswordSpecificsData decrypted;
+ EXPECT_TRUE(cryptographer.Decrypt(encrypted, &decrypted));
+ EXPECT_EQ(original.SerializeAsString(), decrypted.SerializeAsString());
+ }
+}
+
+} // anonymous namespace
+} // namespace browser_sync
diff --git a/chrome/browser/sync/util/nigori.cc b/chrome/browser/sync/util/nigori.cc
index 9334e56..01de18d 100644
--- a/chrome/browser/sync/util/nigori.cc
+++ b/chrome/browser/sync/util/nigori.cc
@@ -69,6 +69,9 @@ Nigori::~Nigori() {
}
bool Nigori::Init(const std::string& username, const std::string& password) {
+ username_ = username;
+ password_ = password;
+
NigoriStream salt_password;
salt_password << username << hostname_;
diff --git a/chrome/browser/sync/util/nigori_unittest.cc b/chrome/browser/sync/util/nigori_unittest.cc
index 04da976..ec42911 100644
--- a/chrome/browser/sync/util/nigori_unittest.cc
+++ b/chrome/browser/sync/util/nigori_unittest.cc
@@ -10,6 +10,14 @@
#include "base/string_util.h"
#include "testing/gtest/include/gtest/gtest.h"
+TEST(NigoriTest, Parameters) {
+ browser_sync::Nigori nigori("example.com");
+ EXPECT_TRUE(nigori.Init("username", "password"));
+ EXPECT_STREQ("example.com", nigori.hostname().c_str());
+ EXPECT_STREQ("username", nigori.username().c_str());
+ EXPECT_STREQ("password", nigori.password().c_str());
+}
+
TEST(NigoriTest, Permute) {
browser_sync::Nigori nigori("example.com");
EXPECT_TRUE(nigori.Init("username", "password"));