diff options
author | albertb@chromium.org <albertb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-01 19:05:48 +0000 |
---|---|---|
committer | albertb@chromium.org <albertb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-01 19:05:48 +0000 |
commit | b7c6924e54a0e9b1244bb86b0d71e1f677fb9f0e (patch) | |
tree | 6d96e2b94dfe74c50a11c097d56c981e542d2928 /chrome | |
parent | 9aed77b6dace047bff676a7cdcfef345a858d1a7 (diff) | |
download | chromium_src-b7c6924e54a0e9b1244bb86b0d71e1f677fb9f0e.zip chromium_src-b7c6924e54a0e9b1244bb86b0d71e1f677fb9f0e.tar.gz chromium_src-b7c6924e54a0e9b1244bb86b0d71e1f677fb9f0e.tar.bz2 |
Move the Nigori classes from base to sync.
BUG=none
TEST=NigoriTest
Review URL: http://codereview.chromium.org/1549012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43366 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/sync/util/nigori.cc | 228 | ||||
-rw-r--r-- | chrome/browser/sync/util/nigori.h | 72 | ||||
-rw-r--r-- | chrome/browser/sync/util/nigori_unittest.cc | 105 | ||||
-rw-r--r-- | chrome/chrome.gyp | 5 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 2 |
5 files changed, 411 insertions, 1 deletions
diff --git a/chrome/browser/sync/util/nigori.cc b/chrome/browser/sync/util/nigori.cc new file mode 100644 index 0000000..b954418 --- /dev/null +++ b/chrome/browser/sync/util/nigori.cc @@ -0,0 +1,228 @@ +// 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/nigori.h" + +#if defined(OS_WIN) +#include <winsock2.h> // for htonl +#endif + +#include <sstream> +#include <vector> + +#include "base/base64.h" +#include "base/crypto/encryptor.h" +#include "base/hmac.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/string_util.h" + +using base::Base64Encode; +using base::Base64Decode; +using base::Encryptor; +using base::HMAC; +using base::RandInt; +using base::SymmetricKey; + +namespace browser_sync { + +// NigoriStream simplifies the concatenation operation of the Nigori protocol. +class NigoriStream { + public: + // Append the big-endian representation of the length of |value| with 32 bits, + // followed by |value| itself to the stream. + NigoriStream& operator<<(const std::string& value) { + uint32 size = htonl(value.size()); + stream_.write((char *) &size, sizeof(uint32)); + stream_ << value; + return *this; + } + + // Append the big-endian representation of the length of |type| with 32 bits, + // followed by the big-endian representation of the value of |type|, with 32 + // bits, to the stream. + NigoriStream& operator<<(const Nigori::Type type) { + uint32 size = htonl(sizeof(uint32)); + stream_.write((char *) &size, sizeof(uint32)); + uint32 value = htonl(type); + stream_.write((char *) &value, sizeof(uint32)); + return *this; + } + + std::string str() { + return stream_.str(); + } + + private: + std::ostringstream stream_; +}; + +// static +const char Nigori::kSaltSalt[] = "saltsalt"; + +Nigori::Nigori(const std::string& hostname) + : hostname_(hostname) { +} + +Nigori::~Nigori() { +} + +bool Nigori::Init(const std::string& username, const std::string& password) { + NigoriStream salt_password; + salt_password << username << hostname_; + + // Suser = PBKDF2(Username || Servername, "saltsalt", Nsalt, 8) + scoped_ptr<SymmetricKey> user_salt(SymmetricKey::DeriveKeyFromPassword( + SymmetricKey::HMAC_SHA1, salt_password.str(), + kSaltSalt, + kSaltIterations, + kSaltKeySize)); + DCHECK(user_salt.get()); + + std::string raw_user_salt; + user_salt->GetRawKey(&raw_user_salt); + + // Kuser = PBKDF2(P, Suser, Nuser, 16) + user_key_.reset(SymmetricKey::DeriveKeyFromPassword(SymmetricKey::AES, + password, raw_user_salt, kUserIterations, kDerivedKeySizeInBits)); + DCHECK(user_key_.get()); + + // Kenc = PBKDF2(P, Suser, Nenc, 16) + encryption_key_.reset(SymmetricKey::DeriveKeyFromPassword(SymmetricKey::AES, + password, raw_user_salt, kEncryptionIterations, kDerivedKeySizeInBits)); + DCHECK(encryption_key_.get()); + + // Kmac = PBKDF2(P, Suser, Nmac, 16) + mac_key_.reset(SymmetricKey::DeriveKeyFromPassword( + SymmetricKey::HMAC_SHA1, password, raw_user_salt, kSigningIterations, + kDerivedKeySizeInBits)); + DCHECK(mac_key_.get()); + + return true; +} + +// Permute[Kenc,Kmac](type || name) +bool Nigori::Permute(Type type, const std::string& name, + std::string* permuted) { + DCHECK_LT(0U, name.size()); + + NigoriStream plaintext; + plaintext << type << name; + + Encryptor encryptor; + if (!encryptor.Init(encryption_key_.get(), Encryptor::CBC, + std::string(kIvSize, 0))) + return false; + + std::string ciphertext; + if (!encryptor.Encrypt(plaintext.str(), &ciphertext)) + return false; + + std::string raw_mac_key; + if (!mac_key_->GetRawKey(&raw_mac_key)) + return false; + + HMAC hmac(HMAC::SHA256); + if (!hmac.Init(raw_mac_key)) + return false; + + std::vector<unsigned char> hash(kHashSize); + if (!hmac.Sign(ciphertext, &hash[0], hash.size())) + return false; + + std::string output; + output.assign(ciphertext); + output.append(hash.begin(), hash.end()); + + return Base64Encode(output, permuted); +} + +std::string GenerateRandomString(size_t size) { + // TODO(albertb): Use a secure random function. + std::string random(size, 0); + for (size_t i = 0; i < size; ++i) + random[i] = RandInt(0, 0xff); + return random; +} + +// Enc[Kenc,Kmac](value) +bool Nigori::Encrypt(const std::string& value, std::string* encrypted) { + DCHECK_LT(0U, value.size()); + + std::string iv = GenerateRandomString(kIvSize); + + Encryptor encryptor; + if (!encryptor.Init(encryption_key_.get(), Encryptor::CBC, iv)) + return false; + + std::string ciphertext; + if (!encryptor.Encrypt(value, &ciphertext)) + return false; + + std::string raw_mac_key; + if (!mac_key_->GetRawKey(&raw_mac_key)) + return false; + + HMAC hmac(HMAC::SHA256); + if (!hmac.Init(raw_mac_key)) + return false; + + std::vector<unsigned char> hash(kHashSize); + if (!hmac.Sign(ciphertext, &hash[0], hash.size())) + return false; + + std::string output; + output.assign(iv); + output.append(ciphertext); + output.append(hash.begin(), hash.end()); + + return Base64Encode(output, encrypted); +} + +bool Nigori::Decrypt(const std::string& encrypted, std::string* value) { + std::string input; + if (!Base64Decode(encrypted, &input)) + return false; + + if (input.size() < kIvSize * 2 + kHashSize) + return false; + + // The input is: + // * iv (16 bytes) + // * ciphertext (multiple of 16 bytes) + // * hash (32 bytes) + std::string iv(input.substr(0, kIvSize)); + std::string ciphertext(input.substr(kIvSize, + input.size() - (kIvSize + kHashSize))); + std::string hash(input.substr(input.size() - kHashSize, kHashSize)); + + std::string raw_mac_key; + if (!mac_key_->GetRawKey(&raw_mac_key)) + return false; + + HMAC hmac(HMAC::SHA256); + if (!hmac.Init(raw_mac_key)) + return false; + + std::vector<unsigned char> expected(kHashSize); + if (!hmac.Sign(ciphertext, &expected[0], expected.size())) + return false; + + if (hash.compare(0, hash.size(), + reinterpret_cast<char *>(&expected[0]), + expected.size())) + return false; + + Encryptor encryptor; + if (!encryptor.Init(encryption_key_.get(), Encryptor::CBC, iv)) + return false; + + std::string plaintext; + if (!encryptor.Decrypt(ciphertext, value)) + return false; + + return true; +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/util/nigori.h b/chrome/browser/sync/util/nigori.h new file mode 100644 index 0000000..67dc758 --- /dev/null +++ b/chrome/browser/sync/util/nigori.h @@ -0,0 +1,72 @@ +// 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_NIGORI_H_ +#define CHROME_BROWSER_SYNC_UTIL_NIGORI_H_ + +#include <string> + +#include "base/crypto/symmetric_key.h" +#include "base/scoped_ptr.h" + +namespace browser_sync { + +// A (partial) implementation of Nigori, a protocol to securely store secrets in +// the cloud. This implementation does not support server authentication or +// assisted key derivation. +// +// To store secrets securely, use the |Permute| method to derive a lookup name +// for your secret (basically a map key), and |Encrypt| and |Decrypt| to store +// and retrieve the secret. +// +// TODO: Link to doc. +class Nigori { + public: + enum Type { + Password = 1, + }; + + // Creates a Nigori client for communicating with |hostname|. Note that + // |hostname| is used to derive the keys used to encrypt and decrypt data. + explicit Nigori(const std::string& hostname); + virtual ~Nigori(); + + // Initialize the client with the supplied |username| and |password|. + bool Init(const std::string& username, const std::string& password); + + // Derives a secure lookup name from |type| and |name|. If |hostname|, + // |username| and |password| are kept constant, a given |type| and |name| pair + // always yields the same |permuted| value. Note that |permuted| will be + // Base64 encoded. + bool Permute(Type type, const std::string& name, std::string* permuted); + + // Encrypts |value|. Note that on success, |encrypted| will be Base64 + // encoded. + bool Encrypt(const std::string& value, std::string* encrypted); + + // Decrypts |value| into |decrypted|. It is assumed that |value| is Base64 + // encoded. + bool Decrypt(const std::string& value, std::string* decrypted); + + static const char kSaltSalt[]; // The salt used to derive the user salt. + static const size_t kSaltKeySize = 8; + static const size_t kDerivedKeySizeInBits = 128; + static const size_t kIvSize = 16; + static const size_t kHashSize = 32; + + static const size_t kSaltIterations = 1001; + static const size_t kUserIterations = 1002; + static const size_t kEncryptionIterations = 1003; + static const size_t kSigningIterations = 1004; + + private: + const std::string hostname_; + scoped_ptr<base::SymmetricKey> user_key_; + scoped_ptr<base::SymmetricKey> encryption_key_; + scoped_ptr<base::SymmetricKey> mac_key_; +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_UTIL_NIGORI_H_ diff --git a/chrome/browser/sync/util/nigori_unittest.cc b/chrome/browser/sync/util/nigori_unittest.cc new file mode 100644 index 0000000..f01d015 --- /dev/null +++ b/chrome/browser/sync/util/nigori_unittest.cc @@ -0,0 +1,105 @@ +// 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/nigori.h" + +#include <string> + +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(USE_NSS) +#define MAYBE(name) name +#else +#define MAYBE(name) DISABLED_ ## name +#endif + +TEST(NigoriTest, MAYBE(PermuteIsConstant)) { + browser_sync::Nigori nigori1("example.com"); + EXPECT_TRUE(nigori1.Init("username", "password")); + + std::string permuted1; + EXPECT_TRUE(nigori1.Permute(browser_sync::Nigori::Password, + "name", + &permuted1)); + + browser_sync::Nigori nigori2("example.com"); + EXPECT_TRUE(nigori2.Init("username", "password")); + + std::string permuted2; + EXPECT_TRUE(nigori2.Permute(browser_sync::Nigori::Password, + "name", + &permuted2)); + + EXPECT_LT(0U, permuted1.size()); + EXPECT_EQ(permuted1, permuted2); +} + +TEST(NigoriTest, MAYBE(EncryptDifferentIv)) { + browser_sync::Nigori nigori("example.com"); + EXPECT_TRUE(nigori.Init("username", "password")); + + std::string plaintext("value"); + + std::string encrypted1; + EXPECT_TRUE(nigori.Encrypt(plaintext, &encrypted1)); + + std::string encrypted2; + EXPECT_TRUE(nigori.Encrypt(plaintext, &encrypted2)); + + EXPECT_NE(encrypted1, encrypted2); +} + +TEST(NigoriTest, MAYBE(EncryptDecrypt)) { + browser_sync::Nigori nigori("example.com"); + EXPECT_TRUE(nigori.Init("username", "password")); + + std::string plaintext("value"); + + std::string encrypted; + EXPECT_TRUE(nigori.Encrypt(plaintext, &encrypted)); + + std::string decrypted; + EXPECT_TRUE(nigori.Decrypt(encrypted, &decrypted)); + + EXPECT_EQ(plaintext, decrypted); +} + +TEST(NigoriTest, MAYBE(CorruptedIv)) { + browser_sync::Nigori nigori("example.com"); + EXPECT_TRUE(nigori.Init("username", "password")); + + std::string plaintext("test"); + + std::string encrypted; + EXPECT_TRUE(nigori.Encrypt(plaintext, &encrypted)); + + // Corrupt the IV by changing one of its byte. + encrypted[0] = (encrypted[0] == 'a' ? 'b' : 'a'); + + std::string decrypted; + EXPECT_TRUE(nigori.Decrypt(encrypted, &decrypted)); + + EXPECT_NE(plaintext, decrypted); +} + +TEST(NigoriTest, MAYBE(CorruptedCiphertext)) { + browser_sync::Nigori nigori("example.com"); + EXPECT_TRUE(nigori.Init("username", "password")); + + std::string plaintext("test"); + + std::string encrypted; + EXPECT_TRUE(nigori.Encrypt(plaintext, &encrypted)); + + // Corrput the ciphertext by changing one of its bytes. + encrypted[browser_sync::Nigori::kIvSize + 10] = + (encrypted[browser_sync::Nigori::kIvSize + 10] == 'a' ? 'b' : 'a'); + + std::string decrypted; + EXPECT_FALSE(nigori.Decrypt(encrypted, &decrypted)); + + EXPECT_NE(plaintext, decrypted); +} diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index e775c50..3c2b6b9 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -961,6 +961,8 @@ 'browser/sync/util/extensions_activity_monitor.cc', 'browser/sync/util/extensions_activity_monitor.h', 'browser/sync/util/fast_dump.h', + 'browser/sync/util/nigori.cc', + 'browser/sync/util/nigori.h', 'browser/sync/util/row_iterator.h', 'browser/sync/util/signin.h', 'browser/sync/util/sync_types.h', @@ -992,7 +994,8 @@ }], ['OS=="linux" or OS=="freebsd" or OS=="openbsd" or OS=="solaris"', { 'dependencies': [ - '../build/linux/system.gyp:gtk' + '../build/linux/system.gyp:gtk', + '../build/linux/system.gyp:nss' ], 'link_settings': { 'libraries': [ diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 07a8207..c5ac745 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1638,6 +1638,7 @@ 'browser/sync/util/crypto_helpers_unittest.cc', 'browser/sync/util/event_sys_unittest.cc', 'browser/sync/util/extensions_activity_monitor_unittest.cc', + 'browser/sync/util/nigori_unittest.cc', 'browser/sync/util/user_settings_unittest.cc', 'test/file_test_utils.cc', 'test/sync/engine/mock_gaia_authenticator.cc', @@ -1704,6 +1705,7 @@ ['OS=="linux"', { 'dependencies': [ '../build/linux/system.gyp:gtk', + '../build/linux/system.gyp:nss', 'packed_resources' ], }], |