implement openssl symmetric key
add AES derived key test

Also includes some build fixes.

BUG=None
TEST=./out/Debug/base_unittests --gtest_filter=SymmetricKey*

Review URL: http://codereview.chromium.org/4691003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@65936 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/base/base.gypi b/base/base.gypi
index bca077a4..a395299 100644
--- a/base/base.gypi
+++ b/base/base.gypi
@@ -121,6 +121,7 @@
           'mac_util.h',
           'mac_util.mm',
           'mac/scoped_aedesc.h',
+          'mac/scoped_cftyperef.h',
           'mac/scoped_nsautorelease_pool.h',
           'mac/scoped_nsautorelease_pool.mm',
           'mach_ipc_mac.h',
@@ -196,7 +197,6 @@
           'safe_strerror_posix.cc',
           'safe_strerror_posix.h',
           'scoped_callback_factory.h',
-          'mac/scoped_cftyperef.h',
           'scoped_handle.h',
           'scoped_native_library.cc',
           'scoped_native_library.h',
@@ -478,6 +478,8 @@
           'nsimage_cache_mac.mm',
           'nss_util.cc',
           'nss_util.h',
+          'openssl_util.cc',
+          'openssl_util.h',
           'setproctitle_linux.c',
           'setproctitle_linux.h',
           'sha2.cc',
@@ -565,6 +567,8 @@
                 'crypto/signature_verifier_openssl.cc',
                 'crypto/symmetric_key_openssl.cc',
                 'hmac_openssl.cc',
+                'openssl_util.cc',
+                'openssl_util.h',
                 'sha2_openssl.cc',
               ],
           },],
diff --git a/base/crypto/symmetric_key.h b/base/crypto/symmetric_key.h
index 3f2be76..97291666 100644
--- a/base/crypto/symmetric_key.h
+++ b/base/crypto/symmetric_key.h
@@ -66,8 +66,8 @@
 
  private:
 #if defined(USE_OPENSSL)
-  // TODO(joth): Add a constructor that accepts OpenSSL symmetric key data, and
-  // the appropriate data members to store it in.
+  SymmetricKey() {}
+  std::string key_;
 #elif defined(USE_NSS)
   explicit SymmetricKey(PK11SymKey* key);
   ScopedPK11SymKey key_;
diff --git a/base/crypto/symmetric_key_openssl.cc b/base/crypto/symmetric_key_openssl.cc
index 591252d..e469135 100644
--- a/base/crypto/symmetric_key_openssl.cc
+++ b/base/crypto/symmetric_key_openssl.cc
@@ -4,18 +4,43 @@
 
 #include "base/crypto/symmetric_key.h"
 
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+
+#include <algorithm>
+
 #include "base/logging.h"
+#include "base/openssl_util.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
 
 namespace base {
 
 SymmetricKey::~SymmetricKey() {
+  std::fill(key_.begin(), key_.end(), '\0');  // Zero out the confidential key.
 }
 
 // static
 SymmetricKey* SymmetricKey::GenerateRandomKey(Algorithm algorithm,
                                               size_t key_size_in_bits) {
-  NOTIMPLEMENTED();
-  return NULL;
+  DCHECK_EQ(AES, algorithm);
+  int key_size_in_bytes = key_size_in_bits / 8;
+  DCHECK_EQ(static_cast<int>(key_size_in_bits), key_size_in_bytes * 8);
+
+  if (key_size_in_bits == 0)
+    return NULL;
+
+  scoped_ptr<SymmetricKey> key(new SymmetricKey);
+  uint8* key_data =
+      reinterpret_cast<uint8*>(WriteInto(&key->key_, key_size_in_bytes + 1));
+
+  int res = RAND_bytes(key_data, key_size_in_bytes);
+  if (res != 1) {
+    DLOG(ERROR) << "RAND_bytes failed. res = " << res;
+    ClearOpenSSLERRStack();
+    return NULL;
+  }
+  return key.release();
 }
 
 // static
@@ -24,20 +49,36 @@
                                                   const std::string& salt,
                                                   size_t iterations,
                                                   size_t key_size_in_bits) {
-  NOTIMPLEMENTED();
-  return NULL;
+  DCHECK(algorithm == AES || algorithm == HMAC_SHA1);
+  int key_size_in_bytes = key_size_in_bits / 8;
+  DCHECK_EQ(static_cast<int>(key_size_in_bits), key_size_in_bytes * 8);
+
+  scoped_ptr<SymmetricKey> key(new SymmetricKey);
+  uint8* key_data =
+      reinterpret_cast<uint8*>(WriteInto(&key->key_, key_size_in_bytes + 1));
+  int res = PKCS5_PBKDF2_HMAC_SHA1(password.data(), password.length(),
+                                   reinterpret_cast<const uint8*>(salt.data()),
+                                   salt.length(), iterations,
+                                   key_size_in_bytes, key_data);
+  if (res != 1) {
+    DLOG(ERROR) << "HMAC SHA1 failed. res = " << res;
+    ClearOpenSSLERRStack();
+    return NULL;
+  }
+  return key.release();
 }
 
 // static
 SymmetricKey* SymmetricKey::Import(Algorithm algorithm,
                                    const std::string& raw_key) {
-  NOTIMPLEMENTED();
-  return NULL;
+  scoped_ptr<SymmetricKey> key(new SymmetricKey);
+  key->key_ = raw_key;
+  return key.release();
 }
 
 bool SymmetricKey::GetRawKey(std::string* raw_key) {
-  NOTIMPLEMENTED();
-  return false;
+  *raw_key = key_;
+  return true;
 }
 
 }  // namespace base
diff --git a/base/crypto/symmetric_key_unittest.cc b/base/crypto/symmetric_key_unittest.cc
index 664bcb50..a9b0b9e 100644
--- a/base/crypto/symmetric_key_unittest.cc
+++ b/base/crypto/symmetric_key_unittest.cc
@@ -7,6 +7,7 @@
 #include <string>
 
 #include "base/scoped_ptr.h"
+#include "base/string_number_conversions.h"
 #include "base/string_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -66,156 +67,160 @@
 }
 
 struct PBKDF2TestVector {
+  base::SymmetricKey::Algorithm algorithm;
   const char* password;
   const char* salt;
   unsigned int rounds;
   unsigned int key_size_in_bits;
-  const uint8 expected[21];  // string literals need 1 extra NUL byte
+  const char* expected;  // ASCII encoded hex bytes
 };
 
-static const PBKDF2TestVector test_vectors[] = {
+class SymmetricKeyDeriveKeyFromPasswordTest
+    : public testing::TestWithParam<PBKDF2TestVector> {
+};
+
+TEST_P(SymmetricKeyDeriveKeyFromPasswordTest, DeriveKeyFromPassword) {
+  PBKDF2TestVector test_data(GetParam());
+#if defined(OS_MACOSX)
+  // The OS X crypto libraries have minimum salt and iteration requirements
+  // so some of the tests below will cause them to barf. Skip these.
+  if (strlen(test_data.salt) < 8 || test_data.rounds < 1000) {
+    VLOG(1) << "Skipped test vector for " << test_data.expected;
+    return;
+  }
+#endif  // OS_MACOSX
+
+  scoped_ptr<base::SymmetricKey> key(
+      base::SymmetricKey::DeriveKeyFromPassword(
+          test_data.algorithm,
+          test_data.password, test_data.salt,
+          test_data.rounds, test_data.key_size_in_bits));
+  ASSERT_TRUE(NULL != key.get());
+
+  std::string raw_key;
+  key->GetRawKey(&raw_key);
+  EXPECT_EQ(test_data.key_size_in_bits / 8, raw_key.size());
+  EXPECT_EQ(test_data.expected,
+            StringToLowerASCII(base::HexEncode(raw_key.data(),
+                                               raw_key.size())));
+}
+
+static const PBKDF2TestVector kTestVectors[] = {
   // These tests come from
   // http://www.ietf.org/id/draft-josefsson-pbkdf2-test-vectors-00.txt
   {
+    base::SymmetricKey::HMAC_SHA1,
     "password",
     "salt",
     1,
     160,
-    "\x0c\x60\xc8\x0f\x96\x1f\x0e\x71\xf3\xa9"
-    "\xb5\x24\xaf\x60\x12\x06\x2f\xe0\x37\xa6",
+    "0c60c80f961f0e71f3a9b524af6012062fe037a6",
   },
   {
+    base::SymmetricKey::HMAC_SHA1,
     "password",
     "salt",
     2,
     160,
-    "\xea\x6c\x01\x4d\xc7\x2d\x6f\x8c\xcd\x1e"
-    "\xd9\x2a\xce\x1d\x41\xf0\xd8\xde\x89\x57",
+    "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957",
   },
   {
+    base::SymmetricKey::HMAC_SHA1,
     "password",
     "salt",
     4096,
     160,
-    "\x4b\x00\x79\x01\xb7\x65\x48\x9a\xbe\xad"
-    "\x49\xd9\x26\xf7\x21\xd0\x65\xa4\x29\xc1",
+    "4b007901b765489abead49d926f721d065a429c1",
   },
   // This test takes over 30s to run on the trybots.
 #if 0
   {
+    base::SymmetricKey::HMAC_SHA1,
     "password",
     "salt",
     16777216,
     160,
-    "\xee\xfe\x3d\x61\xcd\x4d\xa4\xe4\xe9\x94"
-    "\x5b\x3d\x6b\xa2\x15\x8c\x26\x34\xe9\x84",
+    "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984",
   },
 #endif
 
   // These tests come from RFC 3962, via BSD source code at
   // http://www.openbsd.org/cgi-bin/cvsweb/src/sbin/bioctl/pbkdf2.c?rev=HEAD&content-type=text/plain
   {
+    base::SymmetricKey::HMAC_SHA1,
     "password",
     "ATHENA.MIT.EDUraeburn",
     1,
     160,
-    {
-      0xcd, 0xed, 0xb5, 0x28, 0x1b, 0xb2, 0xf8, 0x01,
-      0x56, 0x5a, 0x11, 0x22, 0xb2, 0x56, 0x35, 0x15,
-      0x0a, 0xd1, 0xf7, 0xa0
-    },
+    "cdedb5281bb2f801565a1122b25635150ad1f7a0",
   },
   {
+    base::SymmetricKey::HMAC_SHA1,
     "password",
     "ATHENA.MIT.EDUraeburn",
     2,
     160,
-    {
-      0x01, 0xdb, 0xee, 0x7f, 0x4a, 0x9e, 0x24, 0x3e,
-      0x98, 0x8b, 0x62, 0xc7, 0x3c, 0xda, 0x93, 0x5d,
-      0xa0, 0x53, 0x78, 0xb9
-    },
+    "01dbee7f4a9e243e988b62c73cda935da05378b9",
   },
   {
+    base::SymmetricKey::HMAC_SHA1,
     "password",
     "ATHENA.MIT.EDUraeburn",
     1200,
     160,
-    {
-      0x5c, 0x08, 0xeb, 0x61, 0xfd, 0xf7, 0x1e, 0x4e,
-      0x4e, 0xc3, 0xcf, 0x6b, 0xa1, 0xf5, 0x51, 0x2b,
-      0xa7, 0xe5, 0x2d, 0xdb
-    },
+    "5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddb",
   },
   {
+    base::SymmetricKey::HMAC_SHA1,
     "password",
     "\0224VxxV4\022", /* 0x1234567878563412 */
     5,
     160,
-    {
-      0xd1, 0xda, 0xa7, 0x86, 0x15, 0xf2, 0x87, 0xe6,
-      0xa1, 0xc8, 0xb1, 0x20, 0xd7, 0x06, 0x2a, 0x49,
-      0x3f, 0x98, 0xd2, 0x03
-    },
+    "d1daa78615f287e6a1c8b120d7062a493f98d203",
   },
   {
+    base::SymmetricKey::HMAC_SHA1,
     "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
     "pass phrase equals block size",
     1200,
     160,
-    {
-      0x13, 0x9c, 0x30, 0xc0, 0x96, 0x6b, 0xc3, 0x2b,
-      0xa5, 0x5f, 0xdb, 0xf2, 0x12, 0x53, 0x0a, 0xc9,
-      0xc5, 0xec, 0x59, 0xf1
-    },
+    "139c30c0966bc32ba55fdbf212530ac9c5ec59f1",
   },
   {
+    base::SymmetricKey::HMAC_SHA1,
     "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
     "pass phrase exceeds block size",
     1200,
     160,
-    {
-      0x9c, 0xca, 0xd6, 0xd4, 0x68, 0x77, 0x0c, 0xd5,
-      0x1b, 0x10, 0xe6, 0xa6, 0x87, 0x21, 0xbe, 0x61,
-      0x1a, 0x8b, 0x4d, 0x28
-    },
+    "9ccad6d468770cd51b10e6a68721be611a8b4d28",
   },
   {
+    base::SymmetricKey::HMAC_SHA1,
     "\360\235\204\236", /* g-clef (0xf09d849e) */
     "EXAMPLE.COMpianist",
     50,
     160,
-    {
-      0x6b, 0x9c, 0xf2, 0x6d, 0x45, 0x45, 0x5a, 0x43,
-      0xa5, 0xb8, 0xbb, 0x27, 0x6a, 0x40, 0x3b, 0x39,
-      0xe7, 0xfe, 0x37, 0xa0
-    },
-  }
+    "6b9cf26d45455a43a5b8bb276a403b39e7fe37a0",
+  },
+
+  // Regression tests for AES keys, derived from the Linux NSS implementation.
+  {
+    base::SymmetricKey::AES,
+    "A test password",
+    "saltsalt",
+    1,
+    256,
+    "44899a7777f0e6e8b752f875f02044b8ac593de146de896f2e8a816e315a36de",
+  },
+  {
+    base::SymmetricKey::AES,
+    "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+    "pass phrase exceeds block size",
+    20,
+    256,
+    "e0739745dc28b8721ba402e05214d2ac1eab54cf72bee1fba388297a09eb493c",
+  },
 };
 
-TEST(SymmetricKeyTest, DeriveKeyFromPassword) {
-  for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_vectors); ++i) {
-    SCOPED_TRACE(StringPrintf("Test[%u]", i));
-#if defined(OS_MACOSX)
-    // The OS X crypto libraries have minimum salt and iteration requirements
-    // so some of the above tests will cause them to barf. Skip these.
-    if (strlen(test_vectors[i].salt) < 8 || test_vectors[i].rounds < 1000) {
-      VLOG(1) << "Skipped test vector #" << i;
-      continue;
-    }
-#endif  // OS_MACOSX
-    scoped_ptr<base::SymmetricKey> key(
-        base::SymmetricKey::DeriveKeyFromPassword(
-            base::SymmetricKey::HMAC_SHA1,
-            test_vectors[i].password, test_vectors[i].salt,
-            test_vectors[i].rounds, test_vectors[i].key_size_in_bits));
-    ASSERT_TRUE(NULL != key.get());
-
-    std::string raw_key;
-    key->GetRawKey(&raw_key);
-    EXPECT_EQ(test_vectors[i].key_size_in_bits / 8, raw_key.size());
-    EXPECT_EQ(0, memcmp(test_vectors[i].expected,
-                        raw_key.data(),
-                        raw_key.size()));
-  }
-}
+INSTANTIATE_TEST_CASE_P(, SymmetricKeyDeriveKeyFromPasswordTest,
+                        testing::ValuesIn(kTestVectors));
diff --git a/base/openssl_util.cc b/base/openssl_util.cc
new file mode 100644
index 0000000..82da868
--- /dev/null
+++ b/base/openssl_util.cc
@@ -0,0 +1,31 @@
+// 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/openssl_util.h"
+
+#include <openssl/err.h>
+
+#include "base/logging.h"
+
+namespace base {
+
+void ClearOpenSSLERRStack() {
+  if (logging::DEBUG_MODE && VLOG_IS_ON(1)) {
+    int error_num = ERR_get_error();
+    if (error_num == 0)
+      return;
+
+    DVLOG(1) << "OpenSSL ERR_get_error stack:";
+    char buf[140];
+    do {
+      ERR_error_string_n(error_num, buf, arraysize(buf));
+      DVLOG(1) << "\t" << error_num << ": " << buf;
+      error_num = ERR_get_error();
+    } while (error_num != 0);
+  } else {
+    ERR_clear_error();
+  }
+}
+
+}  // namespace base
diff --git a/base/openssl_util.h b/base/openssl_util.h
index 4f564cf6..a3f64d4d 100644
--- a/base/openssl_util.h
+++ b/base/openssl_util.h
@@ -2,11 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef BASE_OPENNSSL_UTIL_H_
-#define BASE_OPENNSSL_UTIL_H_
+#ifndef BASE_OPENSSL_UTIL_H_
+#define BASE_OPENSSL_UTIL_H_
 #pragma once
 
 #include "base/basictypes.h"
+#include "base/tracked.h"
 
 namespace base {
 
@@ -46,8 +47,25 @@
   // Temporary buffer writen into in the case where the caller's
   // buffer is not of sufficient size.
   unsigned char min_sized_buffer_[MIN_SIZE];
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedOpenSSLSafeSizeBuffer);
+};
+
+// Drains the OpenSSL ERR_get_error stack. On a debug build the error codes
+// are send to VLOG(1), on a release build they are disregarded.
+void ClearOpenSSLERRStack();
+
+// Put an instance of this class on the call stack to automatically clear the
+// OpenSSL error stack on exit of your function.
+class ScopedOpenSSLERRClearer {
+ public:
+  ScopedOpenSSLERRClearer() {}
+  ~ScopedOpenSSLERRClearer() { ClearOpenSSLERRStack(); }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedOpenSSLERRClearer);
 };
 
 }  // namespace base
 
-#endif  // BASE_NSS_UTIL_H_
+#endif  // BASE_OPENSSL_UTIL_H_