diff options
author | zea@chromium.org <zea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-13 21:52:28 +0000 |
---|---|---|
committer | zea@chromium.org <zea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-13 21:52:28 +0000 |
commit | 19fb909bb05f2574c3fc0f16455c68b6143b2e75 (patch) | |
tree | 4c5eb62def367c4c58422b3f3524cc56e8bfba57 /sync/util | |
parent | 8c55c673a845f9e3d8556c9e755d3247c051800a (diff) | |
download | chromium_src-19fb909bb05f2574c3fc0f16455c68b6143b2e75.zip chromium_src-19fb909bb05f2574c3fc0f16455c68b6143b2e75.tar.gz chromium_src-19fb909bb05f2574c3fc0f16455c68b6143b2e75.tar.bz2 |
[Sync] Implement keystore migration support.
We'll now trigger migration if the keystore key is available, the cryptographer
is ready, and the nigori node isn't already properly migrated. Note that this
means we won't trigger migration without at least the implicit gaia password
already available to the cryptographer, in order to support backwards
compatibility with older clients. Eventually that will change.
In addition, once a nigori node has been migrated, any client that supports
keystore encryption will follow the new encryption constraints, whether
or not the --sync-keystore-encryption flag is passed. This means that if
the user sets a custom passphrase, encrypt everything will also be enabled
(and vice versa).
Migration-aware conflict resolution is not implemented yet.
BUG=129665
Review URL: https://chromiumcodereview.appspot.com/10916036
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@156646 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sync/util')
-rw-r--r-- | sync/util/cryptographer.cc | 179 | ||||
-rw-r--r-- | sync/util/cryptographer.h | 50 | ||||
-rw-r--r-- | sync/util/cryptographer_unittest.cc | 32 |
3 files changed, 167 insertions, 94 deletions
diff --git a/sync/util/cryptographer.cc b/sync/util/cryptographer.cc index 202480d..61b5e63 100644 --- a/sync/util/cryptographer.cc +++ b/sync/util/cryptographer.cc @@ -7,6 +7,7 @@ #include <algorithm> #include "base/base64.h" +#include "base/basictypes.h" #include "base/logging.h" #include "sync/protocol/nigori_specifics.pb.h" #include "sync/util/encryptor.h" @@ -35,9 +36,11 @@ void Cryptographer::Bootstrap(const std::string& restored_bootstrap_token) { return; } - scoped_ptr<Nigori> nigori(UnpackBootstrapToken(restored_bootstrap_token)); - if (nigori.get()) - AddKeyImpl(nigori.Pass()); + std::string serialized_nigori_key = + UnpackBootstrapToken(restored_bootstrap_token); + if (serialized_nigori_key.empty()) + return; + ImportNigoriKey(serialized_nigori_key); } bool Cryptographer::CanDecrypt(const sync_pb::EncryptedData& data) const { @@ -58,12 +61,6 @@ bool Cryptographer::Encrypt( LOG(ERROR) << "Cryptographer not ready, failed to encrypt."; return false; } - NigoriMap::const_iterator default_nigori = - nigoris_.find(default_nigori_name_); - if (default_nigori == nigoris_.end()) { - LOG(ERROR) << "Corrupt default key."; - return false; - } std::string serialized; if (!message.SerializeToString(&serialized)) { @@ -71,6 +68,12 @@ bool Cryptographer::Encrypt( return false; } + return EncryptString(serialized, encrypted); +} + +bool Cryptographer::EncryptString( + const std::string& serialized, + sync_pb::EncryptedData* encrypted) const { if (CanDecryptUsingDefaultKey(*encrypted)) { const std::string& original_serialized = DecryptToString(*encrypted); if (original_serialized == serialized) { @@ -79,6 +82,13 @@ bool Cryptographer::Encrypt( } } + NigoriMap::const_iterator default_nigori = + nigoris_.find(default_nigori_name_); + if (default_nigori == nigoris_.end()) { + LOG(ERROR) << "Corrupt default key."; + return false; + } + encrypted->set_key_name(default_nigori_name_); if (!default_nigori->second->Encrypt(serialized, encrypted->mutable_blob())) { @@ -140,26 +150,52 @@ bool Cryptographer::AddKey(const KeyParams& params) { NOTREACHED(); // Invalid username or password. return false; } - return AddKeyImpl(nigori.Pass()); + return AddKeyImpl(nigori.Pass(), true); +} + +bool Cryptographer::AddNonDefaultKey(const KeyParams& params) { + DCHECK(is_initialized()); + // Create the new Nigori and add it to the keybag. + scoped_ptr<Nigori> nigori(new Nigori); + if (!nigori->InitByDerivation(params.hostname, + params.username, + params.password)) { + NOTREACHED(); // Invalid username or password. + return false; + } + return AddKeyImpl(nigori.Pass(), false); } bool Cryptographer::AddKeyFromBootstrapToken( const std::string restored_bootstrap_token) { // Create the new Nigori and make it the default encryptor. - scoped_ptr<Nigori> nigori(UnpackBootstrapToken(restored_bootstrap_token)); - if (!nigori.get()) - return false; - return AddKeyImpl(nigori.Pass()); + std::string serialized_nigori_key = UnpackBootstrapToken( + restored_bootstrap_token); + return ImportNigoriKey(serialized_nigori_key); } -bool Cryptographer::AddKeyImpl(scoped_ptr<Nigori> initialized_nigori) { +bool Cryptographer::AddKeyImpl(scoped_ptr<Nigori> initialized_nigori, + bool set_as_default) { std::string name; if (!initialized_nigori->Permute(Nigori::Password, kNigoriKeyName, &name)) { NOTREACHED(); return false; } + nigoris_[name] = make_linked_ptr(initialized_nigori.release()); - default_nigori_name_ = name; + + // Check if the key we just added can decrypt the pending keys and add them + // too if so. + if (pending_keys_.get() && CanDecrypt(*pending_keys_)) { + sync_pb::NigoriKeyBag pending_bag; + Decrypt(*pending_keys_, &pending_bag); + InstallKeyBag(pending_bag); + SetDefaultKey(pending_keys_->key_name()); + pending_keys_.reset(); + } + + // The just-added key takes priority over the pending keys as default. + if (set_as_default) SetDefaultKey(name); return true; } @@ -215,34 +251,9 @@ bool Cryptographer::DecryptPendingKeys(const KeyParams& params) { bool Cryptographer::GetBootstrapToken(std::string* token) const { DCHECK(token); - if (!is_initialized()) - return false; - - NigoriMap::const_iterator default_nigori = - nigoris_.find(default_nigori_name_); - if (default_nigori == nigoris_.end()) - return false; - return PackBootstrapToken(default_nigori->second.get(), token); -} - -bool Cryptographer::PackBootstrapToken(const Nigori* nigori, - std::string* pack_into) const { - DCHECK(pack_into); - DCHECK(nigori); - - sync_pb::NigoriKey key; - if (!nigori->ExportKeys(key.mutable_user_key(), - key.mutable_encryption_key(), - key.mutable_mac_key())) { - NOTREACHED(); + std::string unencrypted_token = GetDefaultNigoriKey(); + if (unencrypted_token.empty()) return false; - } - - std::string unencrypted_token; - if (!key.SerializeToString(&unencrypted_token)) { - NOTREACHED(); - return false; - } std::string encrypted_token; if (!encryptor_->EncryptString(unencrypted_token, &encrypted_token)) { @@ -250,43 +261,30 @@ bool Cryptographer::PackBootstrapToken(const Nigori* nigori, return false; } - if (!base::Base64Encode(encrypted_token, pack_into)) { + if (!base::Base64Encode(encrypted_token, token)) { NOTREACHED(); return false; } return true; } -Nigori* Cryptographer::UnpackBootstrapToken(const std::string& token) const { +std::string Cryptographer::UnpackBootstrapToken( + const std::string& token) const { if (token.empty()) - return NULL; + return ""; std::string encrypted_data; if (!base::Base64Decode(token, &encrypted_data)) { DLOG(WARNING) << "Could not decode token."; - return NULL; + return ""; } std::string unencrypted_token; if (!encryptor_->DecryptString(encrypted_data, &unencrypted_token)) { DLOG(WARNING) << "Decryption of bootstrap token failed."; - return NULL; + return ""; } - - sync_pb::NigoriKey key; - if (!key.ParseFromString(unencrypted_token)) { - DLOG(WARNING) << "Parsing of bootstrap token failed."; - return NULL; - } - - scoped_ptr<Nigori> nigori(new Nigori); - if (!nigori->InitByImport(key.user_key(), key.encryption_key(), - key.mac_key())) { - NOTREACHED(); - return NULL; - } - - return nigori.release(); + return unencrypted_token; } void Cryptographer::InstallKeyBag(const sync_pb::NigoriKeyBag& bag) { @@ -307,4 +305,59 @@ void Cryptographer::InstallKeyBag(const sync_pb::NigoriKeyBag& bag) { } } +bool Cryptographer::KeybagIsStale( + const sync_pb::EncryptedData& encrypted_bag) const { + if (!is_ready()) + return false; + if (encrypted_bag.blob().empty()) + return true; + if (!CanDecrypt(encrypted_bag)) + return false; + if (!CanDecryptUsingDefaultKey(encrypted_bag)) + return true; + sync_pb::NigoriKeyBag bag; + if (!Decrypt(encrypted_bag, &bag)) { + LOG(ERROR) << "Failed to decrypt keybag for stale check. " + << "Assuming keybag is corrupted."; + return true; + } + if (static_cast<size_t>(bag.key_size()) < nigoris_.size()) + return true; + return false; +} + +std::string Cryptographer::GetDefaultNigoriKey() const { + if (!is_initialized()) + return ""; + NigoriMap::const_iterator iter = nigoris_.find(default_nigori_name_); + if (iter == nigoris_.end()) + return ""; + sync_pb::NigoriKey key; + if (!iter->second->ExportKeys(key.mutable_user_key(), + key.mutable_encryption_key(), + key.mutable_mac_key())) + return ""; + return key.SerializeAsString(); +} + +bool Cryptographer::ImportNigoriKey(const std::string serialized_nigori_key) { + if (serialized_nigori_key.empty()) + return false; + + sync_pb::NigoriKey key; + if (!key.ParseFromString(serialized_nigori_key)) + return false; + + scoped_ptr<Nigori> nigori(new Nigori); + if (!nigori->InitByImport(key.user_key(), key.encryption_key(), + key.mac_key())) { + NOTREACHED(); + return false; + } + + if (!AddKeyImpl(nigori.Pass(), true)) + return false; + return true; +} + } // namespace syncer diff --git a/sync/util/cryptographer.h b/sync/util/cryptographer.h index 99ae7d2..1a3ee3a 100644 --- a/sync/util/cryptographer.h +++ b/sync/util/cryptographer.h @@ -80,6 +80,15 @@ class Cryptographer { bool Encrypt(const ::google::protobuf::MessageLite& message, sync_pb::EncryptedData* encrypted) const; + // Encrypted |serialized| into |encrypted|. Does not overwrite |encrypted| if + // |message| already matches the decrypted data within |encrypted| and + // |encrypted| was encrypted with the current default key. This avoids + // unnecessarily modifying |encrypted| if the change had no practical effect. + // Returns true unless encryption fails or |message| isn't valid (e.g. a + // required field isn't set). + bool EncryptString(const std::string& serialized, + 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, @@ -96,20 +105,32 @@ class Cryptographer { // 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. + // Will decrypt the pending keys and install them if possible (pending key + // will not overwrite default). bool AddKey(const KeyParams& params); // Same as AddKey(..), but builds the new Nigori from a previously persisted // bootstrap token. This can be useful when consuming a bootstrap token // with a cryptographer that has already been initialized. + // Updates the default key. + // Will decrypt the pending keys and install them if possible (pending key + // will not overwrite default). bool AddKeyFromBootstrapToken(const std::string restored_bootstrap_token); + // Creates a new Nigori instance using |params|. If successful, |params| + // will be added to the nigori keybag, but will not be the default encryption + // key (default_nigori_ will remain the same). + // Prereq: is_initialized() must be true. + // Will decrypt the pending keys and install them if possible (pending key + // will become the new default). + bool AddNonDefaultKey(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. - // Does not update the default nigori. + // Does not modify the default key. void InstallKeys(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. @@ -150,9 +171,19 @@ class Cryptographer { Encryptor* encryptor() const { return encryptor_; } - private: - FRIEND_TEST_ALL_PREFIXES(SyncCryptographerTest, PackUnpack); + // Returns true if |keybag| is decryptable and either is a subset of nigoris_ + // and/or has a different default key. + bool KeybagIsStale(const sync_pb::EncryptedData& keybag) const; + // Returns a serialized sync_pb::NigoriKey version of current default + // encryption key. + std::string GetDefaultNigoriKey() const; + + // Generates a new Nigori from |serialized_nigori_key|, and if successful + // installs the new nigori as the default key. + bool ImportNigoriKey(const std::string serialized_nigori_key); + + private: typedef std::map<std::string, linked_ptr<const Nigori> > NigoriMap; // Helper method to instantiate Nigori instances for each set of key @@ -160,13 +191,12 @@ class Cryptographer { // Does not update the default nigori. void InstallKeyBag(const sync_pb::NigoriKeyBag& bag); - // Helper method to add a nigori as the default key. - bool AddKeyImpl(scoped_ptr<Nigori> nigori); + // Helper method to add a nigori to the keybag, optionally making it the + // default as well. + bool AddKeyImpl(scoped_ptr<Nigori> nigori, bool set_as_default); - // Functions to serialize + encrypt a Nigori object in an opaque format for - // persistence by sync infrastructure. - bool PackBootstrapToken(const Nigori* nigori, std::string* pack_into) const; - Nigori* UnpackBootstrapToken(const std::string& token) const; + // Helper to unencrypt a bootstrap token into a serialized sync_pb::NigoriKey. + std::string UnpackBootstrapToken(const std::string& token) const; Encryptor* const encryptor_; diff --git a/sync/util/cryptographer_unittest.cc b/sync/util/cryptographer_unittest.cc index 1e06b86..9d1b236 100644 --- a/sync/util/cryptographer_unittest.cc +++ b/sync/util/cryptographer_unittest.cc @@ -183,32 +183,22 @@ TEST_F(SyncCryptographerTest, MAYBE_EncryptExportDecrypt) { } } -// Crashes, Bug 55178. -#if defined(OS_WIN) -#define MAYBE_PackUnpack DISABLED_PackUnpack -#else -#define MAYBE_PackUnpack PackUnpack -#endif -TEST_F(SyncCryptographerTest, MAYBE_PackUnpack) { - Nigori nigori; - ASSERT_TRUE(nigori.InitByDerivation("example.com", "username", "password")); - std::string expected_user, expected_encryption, expected_mac; - ASSERT_TRUE(nigori.ExportKeys(&expected_user, &expected_encryption, - &expected_mac)); +TEST_F(SyncCryptographerTest, Bootstrap) { + KeyParams params = {"localhost", "dummy", "dummy"}; + cryptographer_.AddKey(params); std::string token; - EXPECT_TRUE(cryptographer_.PackBootstrapToken(&nigori, &token)); + EXPECT_TRUE(cryptographer_.GetBootstrapToken(&token)); EXPECT_TRUE(IsStringUTF8(token)); - scoped_ptr<Nigori> unpacked(cryptographer_.UnpackBootstrapToken(token)); - EXPECT_NE(static_cast<Nigori*>(NULL), unpacked.get()); + Cryptographer other_cryptographer(&encryptor_); + other_cryptographer.Bootstrap(token); + EXPECT_TRUE(other_cryptographer.is_ready()); - std::string user_key, encryption_key, mac_key; - ASSERT_TRUE(unpacked->ExportKeys(&user_key, &encryption_key, &mac_key)); - - EXPECT_EQ(expected_user, user_key); - EXPECT_EQ(expected_encryption, encryption_key); - EXPECT_EQ(expected_mac, mac_key); + const char secret[] = "secret"; + sync_pb::EncryptedData encrypted; + EXPECT_TRUE(other_cryptographer.EncryptString(secret, &encrypted)); + EXPECT_TRUE(cryptographer_.CanDecryptUsingDefaultKey(encrypted)); } } // namespace syncer |