summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authoralbertb@chromium.org <albertb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-01 19:05:48 +0000
committeralbertb@chromium.org <albertb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-01 19:05:48 +0000
commitb7c6924e54a0e9b1244bb86b0d71e1f677fb9f0e (patch)
tree6d96e2b94dfe74c50a11c097d56c981e542d2928 /chrome
parent9aed77b6dace047bff676a7cdcfef345a858d1a7 (diff)
downloadchromium_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.cc228
-rw-r--r--chrome/browser/sync/util/nigori.h72
-rw-r--r--chrome/browser/sync/util/nigori_unittest.cc105
-rw-r--r--chrome/chrome.gyp5
-rw-r--r--chrome/chrome_tests.gypi2
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'
],
}],