diff options
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/sync/protocol/encryption.proto | 27 | ||||
-rw-r--r-- | chrome/browser/sync/protocol/nigori_specifics.proto | 35 | ||||
-rwxr-xr-x | chrome/browser/sync/protocol/sync_proto.gyp | 2 | ||||
-rw-r--r-- | chrome/browser/sync/util/cryptographer.cc | 157 | ||||
-rw-r--r-- | chrome/browser/sync/util/cryptographer.h | 107 | ||||
-rw-r--r-- | chrome/browser/sync/util/cryptographer_unittest.cc | 134 | ||||
-rw-r--r-- | chrome/browser/sync/util/nigori.cc | 3 | ||||
-rw-r--r-- | chrome/browser/sync/util/nigori_unittest.cc | 8 |
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")); |