shill: Infrastucture for encrypting/decrypting/scrambling store values.

This adds a CryptoProvider that registers and provides access to different
prioritized crypto modules (currently DES-CBC and ROT47). Use the provider in
KeyFileStore to provide an API for getting and setting crypted string values.

BUG=chromium-os:16963
TEST=unit tests

Change-Id: I492516890eb3f527758d354cd8890088cb99dea4
Reviewed-on: http://gerrit.chromium.org/gerrit/3395
Tested-by: Darin Petkov <petkov@chromium.org>
Reviewed-by: Chris Masone <cmasone@chromium.org>
diff --git a/Makefile b/Makefile
index 2c99d8d..6369018 100644
--- a/Makefile
+++ b/Makefile
@@ -45,6 +45,7 @@
 	cellular.o \
 	cellular_service.o \
 	crypto_des_cbc.o \
+	crypto_provider.o \
 	crypto_rot47.o \
 	dbus_adaptor.o \
 	dbus_control.o \
@@ -83,6 +84,7 @@
 TEST_OBJS = \
 	cellular_unittest.o \
 	crypto_des_cbc_unittest.o \
+	crypto_provider_unittest.o \
 	crypto_rot47_unittest.o \
 	dbus_adaptor_unittest.o \
 	device_info_unittest.o \
diff --git a/crypto_des_cbc.h b/crypto_des_cbc.h
index 3a3d84f..bce7102 100644
--- a/crypto_des_cbc.h
+++ b/crypto_des_cbc.h
@@ -7,6 +7,7 @@
 
 #include <vector>
 
+#include <base/basictypes.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
 #include "shill/crypto_interface.h"
@@ -22,7 +23,7 @@
  public:
   static const char kID[];
 
-  CryptoDESCBC(GLib *glib);
+  explicit CryptoDESCBC(GLib *glib);
 
   // Sets the DES key to the last |kBlockSize| bytes of |key_matter_path| and
   // the DES initialization vector to the second to last |kBlockSize| bytes of
@@ -48,6 +49,8 @@
   GLib *glib_;
   std::vector<char> key_;
   std::vector<char> iv_;
+
+  DISALLOW_COPY_AND_ASSIGN(CryptoDESCBC);
 };
 
 }  // namespace shill
diff --git a/crypto_provider.cc b/crypto_provider.cc
new file mode 100644
index 0000000..1ca4b04
--- /dev/null
+++ b/crypto_provider.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2011 The Chromium OS 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 "shill/crypto_provider.h"
+
+#include <base/logging.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/string_util.h>
+
+#include "shill/crypto_des_cbc.h"
+#include "shill/crypto_rot47.h"
+
+using std::string;
+
+namespace shill {
+
+const char CryptoProvider::kKeyMatterFile[] = "/var/lib/whitelist/owner.key";
+
+CryptoProvider::CryptoProvider(GLib *glib)
+    : glib_(glib),
+      key_matter_file_(kKeyMatterFile) {}
+
+void CryptoProvider::Init() {
+  cryptos_.reset();
+
+  // Register the crypto modules in priority order -- highest priority first.
+  scoped_ptr<CryptoDESCBC> des_cbc(new CryptoDESCBC(glib_));
+  if (des_cbc->LoadKeyMatter(key_matter_file_)) {
+    cryptos_.push_back(des_cbc.release());
+  }
+  cryptos_.push_back(new CryptoROT47());
+}
+
+string CryptoProvider::Encrypt(const string &plaintext) {
+  for (Cryptos::iterator it = cryptos_.begin(); it != cryptos_.end(); ++it) {
+    CryptoInterface *crypto = *it;
+    string ciphertext;
+    if (crypto->Encrypt(plaintext, &ciphertext)) {
+      const string prefix = crypto->GetID() + ":";
+      return prefix + ciphertext;
+    }
+  }
+  LOG(WARNING) << "Unable to encrypt text, returning as is.";
+  return plaintext;
+}
+
+string CryptoProvider::Decrypt(const string &ciphertext) {
+  for (Cryptos::iterator it = cryptos_.begin(); it != cryptos_.end(); ++it) {
+    CryptoInterface *crypto = *it;
+    const string prefix = crypto->GetID() + ":";
+    if (StartsWithASCII(ciphertext, prefix, true)) {
+      string to_decrypt = ciphertext;
+      to_decrypt.erase(0, prefix.size());
+      string plaintext;
+      LOG_IF(WARNING, !crypto->Decrypt(to_decrypt, &plaintext))
+          << "Crypto module " << crypto->GetID() << " failed to decrypt.";
+      return plaintext;
+    }
+  }
+  LOG(WARNING) << "Unable to decrypt text, returning as is.";
+  return ciphertext;
+}
+
+}  // namespace shill
diff --git a/crypto_provider.h b/crypto_provider.h
new file mode 100644
index 0000000..9bed7d4
--- /dev/null
+++ b/crypto_provider.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2011 The Chromium OS 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 SHILL_CRYPTO_PROVIDER_
+#define SHILL_CRYPTO_PROVIDER_
+
+#include <base/file_path.h>
+#include <base/memory/scoped_vector.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+namespace shill {
+
+class CryptoInterface;
+class GLib;
+
+class CryptoProvider {
+ public:
+  explicit CryptoProvider(GLib *glib);
+
+  void Init();
+
+  // Returns |plaintext| encrypted by the highest priority available crypto
+  // module capable of performing the operation. If no module succeeds, returns
+  // |plaintext| as is.
+  std::string Encrypt(const std::string &plaintext);
+
+  // Returns |ciphertext| decrypted by the highest priority available crypto
+  // module capable of performing the operation. If no module succeeds, returns
+  // |ciphertext| as is.
+  std::string Decrypt(const std::string &ciphertext);
+
+  void set_key_matter_file(const FilePath &path) { key_matter_file_ = path; }
+
+ private:
+  FRIEND_TEST(CryptoProviderTest, Init);
+  FRIEND_TEST(KeyFileStoreTest, OpenClose);
+  typedef ScopedVector<CryptoInterface> Cryptos;
+
+  static const char kKeyMatterFile[];
+
+  GLib *glib_;
+
+  // Registered crypto modules in high to low priority order.
+  Cryptos cryptos_;
+
+  FilePath key_matter_file_;
+
+  DISALLOW_COPY_AND_ASSIGN(CryptoProvider);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CRYPTO_PROVIDER_
diff --git a/crypto_provider_unittest.cc b/crypto_provider_unittest.cc
new file mode 100644
index 0000000..316b94f
--- /dev/null
+++ b/crypto_provider_unittest.cc
@@ -0,0 +1,97 @@
+// Copyright (c) 2011 The Chromium OS 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/file_util.h>
+#include <base/memory/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "shill/crypto_des_cbc.h"
+#include "shill/crypto_provider.h"
+#include "shill/crypto_rot47.h"
+#include "shill/glib.h"
+
+using std::string;
+using testing::Test;
+
+namespace shill {
+
+namespace {
+const char kTestKey[] = "12345678";
+const char kTestIV[] = "abcdefgh";
+const char kKeyMatterFile[] = "key-matter-file";
+const char kEmptyText[] = "";
+const char kPlainText[] = "This is a test!";
+const char kROT47Text[] = "rot47:%9:D :D 2 E6DEP";
+const char kDESCBCText[] = "des-cbc:02:bKlHDISdHMFc0teQd4mAVrXgwlSj6iA+";
+}  // namespace {}
+
+class CryptoProviderTest : public Test {
+ public:
+  CryptoProviderTest() : provider_(&glib_) {}
+
+ protected:
+  FilePath InitKeyMatterFile(const FilePath &dir);
+
+  GLib glib_;  // Use actual GLib for testing.
+  CryptoProvider provider_;
+};
+
+FilePath CryptoProviderTest::InitKeyMatterFile(const FilePath &dir) {
+  FilePath path = dir.Append(kKeyMatterFile);
+  string matter = string(kTestIV) + kTestKey;
+  file_util::WriteFile(path, matter.data(), matter.size());
+  return path;
+}
+
+TEST_F(CryptoProviderTest, Init) {
+  EXPECT_EQ(CryptoProvider::kKeyMatterFile, provider_.key_matter_file_.value());
+
+  provider_.set_key_matter_file(FilePath("/some/non/existent/file"));
+  provider_.Init();
+  EXPECT_EQ(1, provider_.cryptos_.size());
+  EXPECT_EQ(CryptoROT47::kID, provider_.cryptos_[0]->GetID());
+
+  ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  provider_.set_key_matter_file(InitKeyMatterFile(temp_dir.path()));
+  provider_.Init();
+  EXPECT_EQ(2, provider_.cryptos_.size());
+  EXPECT_EQ(CryptoDESCBC::kID, provider_.cryptos_[0]->GetID());
+  EXPECT_EQ(CryptoROT47::kID, provider_.cryptos_[1]->GetID());
+
+  provider_.set_key_matter_file(FilePath("/other/missing/file"));
+  provider_.Init();
+  EXPECT_EQ(1, provider_.cryptos_.size());
+  EXPECT_EQ(CryptoROT47::kID, provider_.cryptos_[0]->GetID());
+}
+
+TEST_F(CryptoProviderTest, Encrypt) {
+  EXPECT_EQ(kPlainText, provider_.Encrypt(kPlainText));
+  EXPECT_EQ(kEmptyText, provider_.Encrypt(kEmptyText));
+
+  provider_.Init();
+  EXPECT_EQ(kROT47Text, provider_.Encrypt(kPlainText));
+
+  ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  provider_.set_key_matter_file(InitKeyMatterFile(temp_dir.path()));
+  provider_.Init();
+  EXPECT_EQ(kDESCBCText, provider_.Encrypt(kPlainText));
+}
+
+TEST_F(CryptoProviderTest, Decrypt) {
+  EXPECT_EQ(kPlainText, provider_.Decrypt(kPlainText));
+  EXPECT_EQ(kEmptyText, provider_.Decrypt(kEmptyText));
+
+  ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  provider_.set_key_matter_file(InitKeyMatterFile(temp_dir.path()));
+  provider_.Init();
+  EXPECT_EQ(kPlainText, provider_.Decrypt(kROT47Text));
+  EXPECT_EQ(kPlainText, provider_.Decrypt(kDESCBCText));
+  EXPECT_EQ(kPlainText, provider_.Decrypt(kPlainText));
+  EXPECT_EQ(kEmptyText, provider_.Decrypt(kEmptyText));
+}
+
+}  // namespace shill
diff --git a/crypto_rot47.cc b/crypto_rot47.cc
index cf70fa3..79c427d 100644
--- a/crypto_rot47.cc
+++ b/crypto_rot47.cc
@@ -10,6 +10,8 @@
 
 const char CryptoROT47::kID[] = "rot47";
 
+CryptoROT47::CryptoROT47() {}
+
 string CryptoROT47::GetID() {
   return kID;
 }
diff --git a/crypto_rot47.h b/crypto_rot47.h
index 06cf6b9..c99242f 100644
--- a/crypto_rot47.h
+++ b/crypto_rot47.h
@@ -5,6 +5,8 @@
 #ifndef SHILL_CRYPTO_ROT47_
 #define SHILL_CRYPTO_ROT47_
 
+#include <base/basictypes.h>
+
 #include "shill/crypto_interface.h"
 
 namespace shill {
@@ -14,10 +16,15 @@
  public:
   static const char kID[];
 
+  CryptoROT47();
+
   // Inherited from CryptoInterface.
   virtual std::string GetID();
   virtual bool Encrypt(const std::string &plaintext, std::string *ciphertext);
   virtual bool Decrypt(const std::string &ciphertext, std::string *plaintext);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CryptoROT47);
 };
 
 }  // namespace shill
diff --git a/key_file_store.cc b/key_file_store.cc
index d844e7e..cce5ea7 100644
--- a/key_file_store.cc
+++ b/key_file_store.cc
@@ -12,7 +12,10 @@
 
 namespace shill {
 
-KeyFileStore::KeyFileStore(GLib *glib) : glib_(glib), key_file_(NULL) {}
+KeyFileStore::KeyFileStore(GLib *glib)
+    : glib_(glib),
+      crypto_(glib),
+      key_file_(NULL) {}
 
 KeyFileStore::~KeyFileStore() {
   ReleaseKeyFile();
@@ -28,6 +31,7 @@
 bool KeyFileStore::Open() {
   CHECK(!path_.empty());
   CHECK(!key_file_);
+  crypto_.Init();
   key_file_ = glib_->KeyFileNew();
   int64 file_size = 0;
   if (!file_util::GetFileSize(path_, &file_size) || file_size == 0) {
@@ -109,7 +113,7 @@
   GError *error = NULL;
   glib_->KeyFileRemoveGroup(key_file_, group.c_str(), &error);
   if (error) {
-    LOG(ERROR) << "Failed to delete group " << group << "): "
+    LOG(ERROR) << "Failed to delete group " << group << ": "
                << glib_->ConvertErrorToMessage(error);
     return false;
   }
@@ -192,4 +196,22 @@
   return true;
 }
 
+bool KeyFileStore::GetCryptedString(const string &group,
+                                    const string &key,
+                                    string *value) {
+  if (!GetString(group, key, value)) {
+    return false;
+  }
+  if (value) {
+    *value = crypto_.Decrypt(*value);
+  }
+  return true;
+}
+
+bool KeyFileStore::SetCryptedString(const string &group,
+                                    const string &key,
+                                    const string &value) {
+  return SetString(group, key, crypto_.Encrypt(value));
+}
+
 }  // namespace shill
diff --git a/key_file_store.h b/key_file_store.h
index a9e2854..3e88de2 100644
--- a/key_file_store.h
+++ b/key_file_store.h
@@ -8,6 +8,7 @@
 #include <base/file_path.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
+#include "shill/crypto_provider.h"
 #include "shill/glib.h"
 #include "shill/store_interface.h"
 
@@ -49,6 +50,12 @@
   virtual bool SetInt(const std::string &group,
                       const std::string &key,
                       int value);
+  virtual bool GetCryptedString(const std::string &group,
+                                const std::string &key,
+                                std::string *value);
+  virtual bool SetCryptedString(const std::string &group,
+                                const std::string &key,
+                                const std::string &value);
 
  private:
   FRIEND_TEST(KeyFileStoreTest, OpenClose);
@@ -57,8 +64,11 @@
   void ReleaseKeyFile();
 
   GLib *glib_;
+  CryptoProvider crypto_;
   GKeyFile *key_file_;
   FilePath path_;
+
+  DISALLOW_COPY_AND_ASSIGN(KeyFileStore);
 };
 
 }  // namespace shill
diff --git a/key_file_store_unittest.cc b/key_file_store_unittest.cc
index 9e52558..a206060 100644
--- a/key_file_store_unittest.cc
+++ b/key_file_store_unittest.cc
@@ -16,6 +16,11 @@
 
 namespace shill {
 
+namespace {
+const char kPlainText[] = "This is a test!";
+const char kROT47Text[] = "rot47:%9:D :D 2 E6DEP";
+}  // namespace {}
+
 class KeyFileStoreTest : public Test {
  public:
   KeyFileStoreTest() : store_(&glib_) {}
@@ -55,6 +60,7 @@
 
   ASSERT_TRUE(store_.Open());
   EXPECT_TRUE(store_.key_file_);
+  EXPECT_EQ(1, store_.crypto_.cryptos_.size());
   ASSERT_TRUE(store_.Close());
   EXPECT_FALSE(store_.key_file_);
 
@@ -288,6 +294,35 @@
             ReadKeyFile());
 }
 
+TEST_F(KeyFileStoreTest, GetCryptedString) {
+  static const char kGroup[] = "crypto-group";
+  static const char kKey[] = "secret";
+  WriteKeyFile(base::StringPrintf("[%s]\n"
+                                  "%s=%s\n",
+                                  kGroup, kKey, kROT47Text));
+  ASSERT_TRUE(store_.Open());
+  string value;
+  EXPECT_TRUE(store_.GetCryptedString(kGroup, kKey, &value));
+  EXPECT_EQ(kPlainText, value);
+  EXPECT_FALSE(store_.GetCryptedString("something-else", kKey, &value));
+  EXPECT_FALSE(store_.GetCryptedString(kGroup, "non-secret", &value));
+  EXPECT_TRUE(store_.GetCryptedString(kGroup, kKey, NULL));
+  ASSERT_TRUE(store_.Close());
+}
+
+TEST_F(KeyFileStoreTest, SetCryptedString) {
+  static const char kGroup[] = "crypted-string-group";
+  static const char kKey[] = "test-string";
+  ASSERT_TRUE(store_.Open());
+  ASSERT_TRUE(store_.SetCryptedString(kGroup, kKey, kPlainText));
+  ASSERT_TRUE(store_.Close());
+  EXPECT_EQ(base::StringPrintf("\n"
+                               "[%s]\n"
+                               "%s=%s\n",
+                               kGroup, kKey, kROT47Text),
+            ReadKeyFile());
+}
+
 TEST_F(KeyFileStoreTest, Combo) {
   static const char kGroupA[] = "square";
   static const char kGroupB[] = "circle";
diff --git a/store_interface.h b/store_interface.h
index cd0fbbd..46a71cc 100644
--- a/store_interface.h
+++ b/store_interface.h
@@ -77,6 +77,19 @@
   virtual bool SetInt(const std::string &group,
                       const std::string &key,
                       int value) = 0;
+
+  // Gets and decrypts string |value| associated with |group|:|key|. Returns
+  // true on success and false on failure (including when |group|:|key| is not
+  // present in the store).
+  virtual bool GetCryptedString(const std::string &group,
+                                const std::string &key,
+                                std::string *value) = 0;
+
+  // Associates |group|:|key| with a string |value| after encrypting it. Returns
+  // true on success, false otherwise.
+  virtual bool SetCryptedString(const std::string &group,
+                                const std::string &key,
+                                const std::string &value) = 0;
 };
 
 }  // namespace shill