summaryrefslogtreecommitdiffstats
path: root/base
diff options
context:
space:
mode:
authoralbertb@chromium.org <albertb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-31 16:18:30 +0000
committeralbertb@chromium.org <albertb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-31 16:18:30 +0000
commit1b47ce2e8d25023f531f4afa8f05b044c4cef111 (patch)
tree7b4875711b3b1aea46b06ad0d2bb84194d0301c7 /base
parent61ee6287a14aed0235a40488394fb700e9c5c43c (diff)
downloadchromium_src-1b47ce2e8d25023f531f4afa8f05b044c4cef111.zip
chromium_src-1b47ce2e8d25023f531f4afa8f05b044c4cef111.tar.gz
chromium_src-1b47ce2e8d25023f531f4afa8f05b044c4cef111.tar.bz2
First pass of a Nigori implementation for Chrome. Only unassisted key
derivation is supported and there is no support for server authentication. BUG=37363 TEST=unit tests Review URL: http://codereview.chromium.org/1357003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43220 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r--base/base.gyp1
-rw-r--r--base/base.gypi2
-rw-r--r--base/crypto/encryptor.h11
-rw-r--r--base/crypto/encryptor_mac.cc8
-rw-r--r--base/crypto/encryptor_nss.cc3
-rw-r--r--base/crypto/symmetric_key_nss.cc1
-rw-r--r--base/hmac.h5
-rw-r--r--base/hmac_nss.cc25
-rw-r--r--base/nigori.cc221
-rw-r--r--base/nigori.h72
-rw-r--r--base/nigori_unittest.cc101
11 files changed, 427 insertions, 23 deletions
diff --git a/base/base.gyp b/base/base.gyp
index 72255de..37c21ff 100644
--- a/base/base.gyp
+++ b/base/base.gyp
@@ -100,6 +100,7 @@
'mac_util_unittest.mm',
'message_loop_unittest.cc',
'message_pump_glib_unittest.cc',
+ 'nigori_unittest.cc',
'object_watcher_unittest.cc',
'observer_list_unittest.cc',
'path_service_unittest.cc',
diff --git a/base/base.gypi b/base/base.gypi
index 907f490..484b7bf 100644
--- a/base/base.gypi
+++ b/base/base.gypi
@@ -535,6 +535,8 @@
'message_pump_libevent.h',
'message_pump_mac.h',
'message_pump_mac.mm',
+ 'nigori.h',
+ 'nigori.cc',
'nsimage_cache_mac.h',
'nsimage_cache_mac.mm',
'nss_util.cc',
diff --git a/base/crypto/encryptor.h b/base/crypto/encryptor.h
index a09c7cd..96a6d6a 100644
--- a/base/crypto/encryptor.h
+++ b/base/crypto/encryptor.h
@@ -17,12 +17,11 @@ class Encryptor {
enum Mode {
CBC
};
- explicit Encryptor();
- ~Encryptor();
+ Encryptor();
+ virtual ~Encryptor();
- // Initializes the encryptor using |key| and |iv|. Takes ownership of |key| if
- // successful. Returns false if either the key or the initialization vector
- // cannot be used.
+ // Initializes the encryptor using |key| and |iv|. Returns false if either the
+ // key or the initialization vector cannot be used.
bool Init(SymmetricKey* key, Mode mode, const std::string& iv);
// Encrypts |plaintext| into |ciphertext|.
@@ -34,8 +33,8 @@ class Encryptor {
// TODO(albertb): Support streaming encryption.
private:
+ SymmetricKey* key_;
Mode mode_;
- scoped_ptr<SymmetricKey> key_;
#if defined(USE_NSS)
ScopedPK11Slot slot_;
diff --git a/base/crypto/encryptor_mac.cc b/base/crypto/encryptor_mac.cc
index 4e8984a..e892c12 100644
--- a/base/crypto/encryptor_mac.cc
+++ b/base/crypto/encryptor_mac.cc
@@ -28,7 +28,7 @@ bool Encryptor::Init(SymmetricKey* key, Mode mode, const std::string& iv) {
if (iv.size() != kCCBlockSizeAES128)
return false;
- key_.reset(key);
+ key_ = key;
mode_ = mode;
iv_ = iv;
return true;
@@ -37,12 +37,12 @@ bool Encryptor::Init(SymmetricKey* key, Mode mode, const std::string& iv) {
bool Encryptor::Crypt(int /*CCOperation*/ op,
const std::string& input,
std::string* output) {
- DCHECK(key_.get());
+ DCHECK(key_);
CSSM_DATA raw_key = key_->cssm_data();
// CommonCryptor.h: "A general rule for the size of the output buffer which
- // must be provided by the caller is that for block ciphers, the output
+ // must be provided by the caller is that for block ciphers, the output
// length is never larger than the input length plus the block size."
-
+
size_t output_size = input.size() + iv_.size();
CCCryptorStatus err = CCCrypt(op,
kCCAlgorithmAES128,
diff --git a/base/crypto/encryptor_nss.cc b/base/crypto/encryptor_nss.cc
index 78ddb64..eac6779 100644
--- a/base/crypto/encryptor_nss.cc
+++ b/base/crypto/encryptor_nss.cc
@@ -23,7 +23,9 @@ bool Encryptor::Init(SymmetricKey* key, Mode mode, const std::string& iv) {
DCHECK(key);
DCHECK_EQ(CBC, mode);
+ key_ = key;
mode_ = mode;
+
if (iv.size() != AES_BLOCK_SIZE)
return false;
@@ -41,7 +43,6 @@ bool Encryptor::Init(SymmetricKey* key, Mode mode, const std::string& iv) {
if (!param_.get())
return false;
- key_.reset(key);
return true;
}
diff --git a/base/crypto/symmetric_key_nss.cc b/base/crypto/symmetric_key_nss.cc
index 0fb8cfa..5af7cde 100644
--- a/base/crypto/symmetric_key_nss.cc
+++ b/base/crypto/symmetric_key_nss.cc
@@ -55,7 +55,6 @@ SymmetricKey* SymmetricKey::DeriveKeyFromPassword(Algorithm algorithm,
const_cast<char *>(salt.data()));
salt_item.len = salt.size();
-
SECOidTag cipher_algorithm =
algorithm == AES ? SEC_OID_AES_256_CBC : SEC_OID_HMAC_SHA1;
ScopedSECAlgorithmID alg_id(PK11_CreatePBEV2AlgorithmID(SEC_OID_PKCS5_PBKDF2,
diff --git a/base/hmac.h b/base/hmac.h
index bdd23a9..b915124 100644
--- a/base/hmac.h
+++ b/base/hmac.h
@@ -22,7 +22,8 @@ class HMAC {
public:
// The set of supported hash functions. Extend as required.
enum HashAlgorithm {
- SHA1
+ SHA1,
+ SHA256,
};
explicit HMAC(HashAlgorithm hash_alg);
@@ -44,6 +45,8 @@ class HMAC {
// returned in |digest|, which has |digest_length| bytes of storage available.
bool Sign(const std::string& data, unsigned char* digest, int digest_length);
+ // TODO(albertb): Add a Verify method.
+
private:
HashAlgorithm hash_alg_;
scoped_ptr<HMACPlatformData> plat_;
diff --git a/base/hmac_nss.cc b/base/hmac_nss.cc
index 8f63d04..b3c0c67 100644
--- a/base/hmac_nss.cc
+++ b/base/hmac_nss.cc
@@ -15,31 +15,36 @@
namespace base {
struct HMACPlatformData {
+ CK_MECHANISM_TYPE mechanism_;
ScopedPK11Slot slot_;
ScopedPK11SymKey sym_key_;
};
HMAC::HMAC(HashAlgorithm hash_alg)
: hash_alg_(hash_alg), plat_(new HMACPlatformData()) {
- // Only SHA-1 digest is supported now.
- DCHECK(hash_alg_ == SHA1);
+ // Only SHA-1 and SHA-256 hash algorithms are supported.
+ switch (hash_alg_) {
+ case SHA1:
+ plat_->mechanism_ = CKM_SHA_1_HMAC;
+ break;
+ case SHA256:
+ plat_->mechanism_ = CKM_SHA256_HMAC;
+ break;
+ default:
+ NOTREACHED() << "Unsupported hash algorithm";
+ }
}
bool HMAC::Init(const unsigned char *key, int key_length) {
base::EnsureNSSInit();
- if (hash_alg_ != SHA1) {
- NOTREACHED();
- return false;
- }
-
if (plat_->slot_.get()) {
// Init must not be called more than twice on the same HMAC object.
NOTREACHED();
return false;
}
- plat_->slot_.reset(PK11_GetBestSlot(CKM_SHA_1_HMAC, NULL));
+ plat_->slot_.reset(PK11_GetBestSlot(plat_->mechanism_, NULL));
if (!plat_->slot_.get()) {
NOTREACHED();
return false;
@@ -51,7 +56,7 @@ bool HMAC::Init(const unsigned char *key, int key_length) {
key_item.len = key_length;
plat_->sym_key_.reset(PK11_ImportSymKey(plat_->slot_.get(),
- CKM_SHA_1_HMAC,
+ plat_->mechanism_,
PK11_OriginUnwrap,
CKA_SIGN,
&key_item,
@@ -77,7 +82,7 @@ bool HMAC::Sign(const std::string& data,
}
SECItem param = { siBuffer, NULL, 0 };
- ScopedPK11Context context(PK11_CreateContextBySymKey(CKM_SHA_1_HMAC,
+ ScopedPK11Context context(PK11_CreateContextBySymKey(plat_->mechanism_,
CKA_SIGN,
plat_->sym_key_.get(),
&param));
diff --git a/base/nigori.cc b/base/nigori.cc
new file mode 100644
index 0000000..753417d
--- /dev/null
+++ b/base/nigori.cc
@@ -0,0 +1,221 @@
+// 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 "base/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"
+
+namespace base {
+
+// 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 base
diff --git a/base/nigori.h b/base/nigori.h
new file mode 100644
index 0000000..df6bf9a
--- /dev/null
+++ b/base/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 BASE_CRYPTO_NIGORI_H_
+#define BASE_CRYPTO_NIGORI_H_
+
+#include <string>
+
+#include "base/crypto/symmetric_key.h"
+#include "base/scoped_ptr.h"
+
+namespace base {
+
+// 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<SymmetricKey> user_key_;
+ scoped_ptr<SymmetricKey> encryption_key_;
+ scoped_ptr<SymmetricKey> mac_key_;
+};
+
+} // namespace base
+
+#endif // BASE_CRYPTO_NIGORI_H_
diff --git a/base/nigori_unittest.cc b/base/nigori_unittest.cc
new file mode 100644
index 0000000..6ebbd00
--- /dev/null
+++ b/base/nigori_unittest.cc
@@ -0,0 +1,101 @@
+// 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 "base/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)) {
+ base::Nigori nigori1("example.com");
+ EXPECT_TRUE(nigori1.Init("username", "password"));
+
+ std::string permuted1;
+ EXPECT_TRUE(nigori1.Permute(base::Nigori::Password, "name", &permuted1));
+
+ base::Nigori nigori2("example.com");
+ EXPECT_TRUE(nigori2.Init("username", "password"));
+
+ std::string permuted2;
+ EXPECT_TRUE(nigori2.Permute(base::Nigori::Password, "name", &permuted2));
+
+ EXPECT_LT(0U, permuted1.size());
+ EXPECT_EQ(permuted1, permuted2);
+}
+
+TEST(NigoriTest, MAYBE(EncryptDifferentIv)) {
+ base::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)) {
+ base::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)) {
+ base::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)) {
+ base::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[base::Nigori::kIvSize + 10] =
+ (encrypted[base::Nigori::kIvSize + 10] == 'a' ? 'b' : 'a');
+
+ std::string decrypted;
+ EXPECT_FALSE(nigori.Decrypt(encrypted, &decrypted));
+
+ EXPECT_NE(plaintext, decrypted);
+}