security: implement PibSqlite3 based on PibImpl

Change-Id: I47a61cf8ebe5f76d5ca492db52c2f4f2f5ac3c9b
Refs: #2807
diff --git a/src/security/pib-sqlite3.cpp b/src/security/pib-sqlite3.cpp
new file mode 100644
index 0000000..3a95b5d
--- /dev/null
+++ b/src/security/pib-sqlite3.cpp
@@ -0,0 +1,557 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2015 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "pib-sqlite3.hpp"
+
+#include "common.hpp"
+#include "pib.hpp"
+#include "util/sqlite3-statement.hpp"
+
+#include <sqlite3.h>
+#include <boost/filesystem.hpp>
+#include <boost/algorithm/string.hpp>
+
+namespace ndn {
+namespace security {
+
+using std::string;
+using util::Sqlite3Statement;
+
+static const string INITIALIZATION =
+  "CREATE TABLE IF NOT EXISTS                    \n"
+  "  tpmInfo(                                    \n"
+  "    tpm_locator           BLOB                \n"
+  "  );                                          \n"
+  "                                              \n"
+  "CREATE TRIGGER IF NOT EXISTS                  \n"
+  "  tpm_update_trigger                          \n"
+  "  BEFORE UPDATE ON tpmInfo                    \n"
+  "  WHEN NEW.tpm_locator!=OLD.tpm_locator       \n"
+  "  BEGIN                                       \n"
+  "    DELETE FROM certificates;                 \n"
+  "    DELETE FROM keys;                         \n"
+  "    DELETE FROM identities;                   \n"
+  "  END;                                        \n"
+  "                                              \n"
+  "                                              \n"
+  "CREATE TABLE IF NOT EXISTS                    \n"
+  "  identities(                                 \n"
+  "    id                    INTEGER PRIMARY KEY,\n"
+  "    identity              BLOB NOT NULL,      \n"
+  "    is_default            INTEGER DEFAULT 0   \n"
+  "  );                                          \n"
+  "                                              \n"
+  "CREATE UNIQUE INDEX IF NOT EXISTS             \n"
+  "  identityIndex ON identities(identity);      \n"
+  "                                              \n"
+  "CREATE TRIGGER IF NOT EXISTS                  \n"
+  "  identity_default_before_insert_trigger      \n"
+  "  BEFORE INSERT ON identities                 \n"
+  "  FOR EACH ROW                                \n"
+  "  WHEN NEW.is_default=1                       \n"
+  "  BEGIN                                       \n"
+  "    UPDATE identities SET is_default=0;       \n"
+  "  END;                                        \n"
+  "                                              \n"
+  "CREATE TRIGGER IF NOT EXISTS                  \n"
+  "  identity_default_after_insert_trigger       \n"
+  "  AFTER INSERT ON identities                  \n"
+  "  FOR EACH ROW                                \n"
+  "  WHEN NOT EXISTS                             \n"
+  "    (SELECT id                                \n"
+  "       FROM identities                        \n"
+  "       WHERE is_default=1)                    \n"
+  "  BEGIN                                       \n"
+  "    UPDATE identities                         \n"
+  "      SET is_default=1                        \n"
+  "      WHERE identity=NEW.identity;            \n"
+  "  END;                                        \n"
+  "                                              \n"
+  "CREATE TRIGGER IF NOT EXISTS                  \n"
+  "  identity_default_update_trigger             \n"
+  "  BEFORE UPDATE ON identities                 \n"
+  "  FOR EACH ROW                                \n"
+  "  WHEN NEW.is_default=1 AND OLD.is_default=0  \n"
+  "  BEGIN                                       \n"
+  "    UPDATE identities SET is_default=0;       \n"
+  "  END;                                        \n"
+  "                                              \n"
+  "                                              \n"
+  "CREATE TABLE IF NOT EXISTS                    \n"
+  "  keys(                                       \n"
+  "    id                    INTEGER PRIMARY KEY,\n"
+  "    identity_id           INTEGER NOT NULL,   \n"
+  "    key_name              BLOB NOT NULL,      \n"
+  "    key_type              INTEGER NOT NULL,   \n"
+  "    key_bits              BLOB NOT NULL,      \n"
+  "    is_default            INTEGER DEFAULT 0,  \n"
+  "    FOREIGN KEY(identity_id)                  \n"
+  "      REFERENCES identities(id)               \n"
+  "      ON DELETE CASCADE                       \n"
+  "      ON UPDATE CASCADE                       \n"
+  "  );                                          \n"
+  "                                              \n"
+  "CREATE UNIQUE INDEX IF NOT EXISTS             \n"
+  "  keyIndex ON keys(key_name);                 \n"
+  "                                              \n"
+  "CREATE TRIGGER IF NOT EXISTS                  \n"
+  "  key_default_before_insert_trigger           \n"
+  "  BEFORE INSERT ON keys                       \n"
+  "  FOR EACH ROW                                \n"
+  "  WHEN NEW.is_default=1                       \n"
+  "  BEGIN                                       \n"
+  "    UPDATE keys                               \n"
+  "      SET is_default=0                        \n"
+  "      WHERE identity_id=NEW.identity_id;      \n"
+  "  END;                                        \n"
+  "                                              \n"
+  "CREATE TRIGGER IF NOT EXISTS                  \n"
+  "  key_default_after_insert_trigger            \n"
+  "  AFTER INSERT ON keys                        \n"
+  "  FOR EACH ROW                                \n"
+  "  WHEN NOT EXISTS                             \n"
+  "    (SELECT id                                \n"
+  "       FROM keys                              \n"
+  "       WHERE is_default=1                     \n"
+  "         AND identity_id=NEW.identity_id)     \n"
+  "  BEGIN                                       \n"
+  "    UPDATE keys                               \n"
+  "      SET is_default=1                        \n"
+  "      WHERE key_name=NEW.key_name;            \n"
+  "  END;                                        \n"
+  "                                              \n"
+  "CREATE TRIGGER IF NOT EXISTS                  \n"
+  "  key_default_update_trigger                  \n"
+  "  BEFORE UPDATE ON keys                       \n"
+  "  FOR EACH ROW                                \n"
+  "  WHEN NEW.is_default=1 AND OLD.is_default=0  \n"
+  "  BEGIN                                       \n"
+  "    UPDATE keys                               \n"
+  "      SET is_default=0                        \n"
+  "      WHERE identity_id=NEW.identity_id;      \n"
+  "  END;                                        \n"
+  "                                              \n"
+  "                                              \n"
+  "CREATE TABLE IF NOT EXISTS                    \n"
+  "  certificates(                               \n"
+  "    id                    INTEGER PRIMARY KEY,\n"
+  "    key_id                INTEGER NOT NULL,   \n"
+  "    certificate_name      BLOB NOT NULL,      \n"
+  "    certificate_data      BLOB NOT NULL,      \n"
+  "    is_default            INTEGER DEFAULT 0,  \n"
+  "    FOREIGN KEY(key_id)                       \n"
+  "      REFERENCES keys(id)                     \n"
+  "      ON DELETE CASCADE                       \n"
+  "      ON UPDATE CASCADE                       \n"
+  "  );                                          \n"
+  "                                              \n"
+  "CREATE UNIQUE INDEX IF NOT EXISTS             \n"
+  "  certIndex ON certificates(certificate_name);\n"
+  "                                              \n"
+  "CREATE TRIGGER IF NOT EXISTS                  \n"
+  "  cert_default_before_insert_trigger          \n"
+  "  BEFORE INSERT ON certificates               \n"
+  "  FOR EACH ROW                                \n"
+  "  WHEN NEW.is_default=1                       \n"
+  "  BEGIN                                       \n"
+  "    UPDATE certificates                       \n"
+  "      SET is_default=0                        \n"
+  "      WHERE key_id=NEW.key_id;                \n"
+  "  END;                                        \n"
+  "                                              \n"
+  "CREATE TRIGGER IF NOT EXISTS                  \n"
+  "  cert_default_after_insert_trigger           \n"
+  "  AFTER INSERT ON certificates                \n"
+  "  FOR EACH ROW                                \n"
+  "  WHEN NOT EXISTS                             \n"
+  "    (SELECT id                                \n"
+  "       FROM certificates                      \n"
+  "       WHERE is_default=1                     \n"
+  "         AND key_id=NEW.key_id)               \n"
+  "  BEGIN                                       \n"
+  "    UPDATE certificates                       \n"
+  "      SET is_default=1                        \n"
+  "      WHERE certificate_name=NEW.certificate_name;\n"
+  "  END;                                        \n"
+  "                                              \n"
+  "CREATE TRIGGER IF NOT EXISTS                  \n"
+  "  cert_default_update_trigger                 \n"
+  "  BEFORE UPDATE ON certificates               \n"
+  "  FOR EACH ROW                                \n"
+  "  WHEN NEW.is_default=1 AND OLD.is_default=0  \n"
+  "  BEGIN                                       \n"
+  "    UPDATE certificates                       \n"
+  "      SET is_default=0                        \n"
+  "      WHERE key_id=NEW.key_id;                \n"
+  "  END;                                        \n";
+
+static Name
+getKeyName(const Name& identity, const name::Component& keyId)
+{
+  Name keyName = identity;
+  keyName.append(keyId);
+  return keyName;
+}
+
+PibSqlite3::PibSqlite3(const string& dir)
+{
+  // Determine the path of PIB DB
+  boost::filesystem::path actualDir;
+  if (dir == "") {
+    if (getenv("HOME") == nullptr)
+      throw PibImpl::Error("Environment variable HOME is not set");
+
+    actualDir = boost::filesystem::path(getenv("HOME")) / ".ndn";
+    boost::filesystem::create_directories(actualDir);
+  }
+  else {
+    actualDir = boost::filesystem::path(dir);
+    boost::filesystem::create_directories(actualDir);
+  }
+  // Open PIB
+  int result = sqlite3_open_v2((actualDir / "pib.db").c_str(), &m_database,
+                               SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
+#ifdef NDN_CXX_DISABLE_SQLITE3_FS_LOCKING
+                               "unix-dotfile"
+#else
+                               nullptr
+#endif
+                               );
+
+  if (result != SQLITE_OK)
+    throw PibImpl::Error("PIB DB cannot be opened/created: " + dir);
+
+
+  // enable foreign key
+  sqlite3_exec(m_database, "PRAGMA foreign_keys=ON", nullptr, nullptr, nullptr);
+
+  // initialize PIB tables
+  char* errorMessage = nullptr;
+  result = sqlite3_exec(m_database, INITIALIZATION.c_str(), nullptr, nullptr, &errorMessage);
+  if (result != SQLITE_OK && errorMessage != nullptr) {
+    sqlite3_free(errorMessage);
+    throw PibImpl::Error("PIB DB cannot be initialized");
+  }
+}
+
+PibSqlite3::~PibSqlite3()
+{
+  sqlite3_close(m_database);
+}
+
+void
+PibSqlite3::setTpmLocator(const std::string& tpmLocator)
+{
+  Sqlite3Statement statement(m_database, "UPDATE tpmInfo SET tpm_locator=?");
+  statement.bind(1, tpmLocator, SQLITE_TRANSIENT);
+  statement.step();
+
+  // no row is updated, tpm_locator does not exist, insert it directly
+  if (0 == sqlite3_changes(m_database)) {
+    Sqlite3Statement insertStatement(m_database, "INSERT INTO tpmInfo (tpm_locator) values (?)");
+    insertStatement.bind(1, tpmLocator, SQLITE_TRANSIENT);
+    insertStatement.step();
+  }
+}
+
+std::string
+PibSqlite3::getTpmLocator() const
+{
+  Sqlite3Statement statement(m_database, "SELECT tpm_locator FROM tpmInfo");
+  int res = statement.step();
+
+  string tpmLocator;
+  if (res == SQLITE_ROW)
+    return statement.getString(0);
+  else
+    throw Pib::Error("TPM info does not exist");
+}
+
+bool
+PibSqlite3::hasIdentity(const Name& identity) const
+{
+  Sqlite3Statement statement(m_database, "SELECT id FROM identities WHERE identity=?");
+  statement.bind(1, identity.wireEncode(), SQLITE_TRANSIENT);
+  return (statement.step() == SQLITE_ROW);
+}
+
+void
+PibSqlite3::addIdentity(const Name& identity)
+{
+  Sqlite3Statement statement(m_database, "INSERT INTO identities (identity) values (?)");
+  statement.bind(1, identity.wireEncode(), SQLITE_TRANSIENT);
+  statement.step();
+}
+
+void
+PibSqlite3::removeIdentity(const Name& identity)
+{
+  Sqlite3Statement statement(m_database, "DELETE FROM identities WHERE identity=?");
+  statement.bind(1, identity.wireEncode(), SQLITE_TRANSIENT);
+  statement.step();
+}
+
+std::set<Name>
+PibSqlite3::getIdentities() const
+{
+  std::set<Name> identities;
+  Sqlite3Statement statement(m_database, "SELECT identity FROM identities");
+
+  while (statement.step() == SQLITE_ROW)
+    identities.insert(Name(statement.getBlock(0)));
+
+  return identities;
+}
+
+void
+PibSqlite3::setDefaultIdentity(const Name& identityName)
+{
+  Sqlite3Statement statement(m_database, "UPDATE identities SET is_default=1 WHERE identity=?");
+  statement.bind(1, identityName.wireEncode(), SQLITE_TRANSIENT);
+  statement.step();
+}
+
+Name
+PibSqlite3::getDefaultIdentity() const
+{
+  Sqlite3Statement statement(m_database, "SELECT identity FROM identities WHERE is_default=1");
+
+  if (statement.step() == SQLITE_ROW)
+    return Name(statement.getBlock(0));
+  else
+    throw Pib::Error("No default identity");
+}
+
+bool
+PibSqlite3::hasKey(const Name& identity, const name::Component& keyId) const
+{
+  Name keyName = getKeyName(identity, keyId);
+
+  Sqlite3Statement statement(m_database, "SELECT id FROM keys WHERE key_name=?");
+  statement.bind(1, keyName.wireEncode(), SQLITE_TRANSIENT);
+
+  return (statement.step() == SQLITE_ROW);
+}
+
+void
+PibSqlite3::addKey(const Name& identity, const name::Component& keyId, const PublicKey& publicKey)
+{
+  if (hasKey(identity, keyId)) {
+    return;
+  }
+
+  // ensure identity exists
+  addIdentity(identity);
+
+  // add key
+  Name keyName = getKeyName(identity, keyId);
+
+  Sqlite3Statement statement(m_database,
+                             "INSERT INTO keys (identity_id, key_name, key_type, key_bits) "
+                             "VALUES ((SELECT id FROM identities WHERE identity=?), ?, ?, ?)");
+  statement.bind(1, identity.wireEncode(), SQLITE_TRANSIENT);
+  statement.bind(2, keyName.wireEncode(), SQLITE_TRANSIENT);
+  statement.bind(3, publicKey.getKeyType());
+  statement.bind(4, publicKey.get().buf(), publicKey.get().size(), SQLITE_STATIC);
+  statement.step();
+}
+
+void
+PibSqlite3::removeKey(const Name& identity, const name::Component& keyId)
+{
+  Name keyName = getKeyName(identity, keyId);
+
+  Sqlite3Statement statement(m_database, "DELETE FROM keys WHERE key_name=?");
+  statement.bind(1, keyName.wireEncode(), SQLITE_TRANSIENT);
+  statement.step();
+}
+
+PublicKey
+PibSqlite3::getKeyBits(const Name& identity, const name::Component& keyId) const
+{
+  Name keyName = getKeyName(identity, keyId);
+
+  Sqlite3Statement statement(m_database, "SELECT key_bits FROM keys WHERE key_name=?");
+  statement.bind(1, keyName.wireEncode(), SQLITE_TRANSIENT);
+
+  if (statement.step() == SQLITE_ROW)
+    return PublicKey(statement.getBlob(0), statement.getSize(0));
+  else
+    throw Pib::Error("Key does not exist");
+}
+
+std::set<name::Component>
+PibSqlite3::getKeysOfIdentity(const Name& identity) const
+{
+  std::set<name::Component> keyNames;
+
+  Sqlite3Statement statement(m_database,
+                             "SELECT key_name "
+                             "FROM keys JOIN identities ON keys.identity_id=identities.id "
+                             "WHERE identities.identity=?");
+  statement.bind(1, identity.wireEncode(), SQLITE_TRANSIENT);
+
+  while (statement.step() == SQLITE_ROW) {
+    Name keyName(statement.getBlock(0));
+    keyNames.insert(keyName.get(-1));
+  }
+
+  return keyNames;
+}
+
+void
+PibSqlite3::setDefaultKeyOfIdentity(const Name& identity, const name::Component& keyId)
+{
+  Name keyName = getKeyName(identity, keyId);
+
+  if (!hasKey(identity, keyId)) {
+    throw Pib::Error("No such key");
+  }
+
+  Sqlite3Statement statement(m_database, "UPDATE keys SET is_default=1 WHERE key_name=?");
+  statement.bind(1, keyName.wireEncode(), SQLITE_TRANSIENT);
+  statement.step();
+}
+
+name::Component
+PibSqlite3::getDefaultKeyOfIdentity(const Name& identity) const
+{
+  if (!hasIdentity(identity)) {
+    throw Pib::Error("Identity does not exist");
+  }
+
+  Sqlite3Statement statement(m_database,
+                             "SELECT key_name "
+                             "FROM keys JOIN identities ON keys.identity_id=identities.id "
+                             "WHERE identities.identity=? AND keys.is_default=1");
+  statement.bind(1, identity.wireEncode(), SQLITE_TRANSIENT);
+
+  if (statement.step() == SQLITE_ROW) {
+    Name keyName(statement.getBlock(0));
+    return keyName.get(-1);
+  }
+  else
+    throw Pib::Error("No default key");
+}
+
+bool
+PibSqlite3::hasCertificate(const Name& certName) const
+{
+  Sqlite3Statement statement(m_database, "SELECT id FROM certificates WHERE certificate_name=?");
+  statement.bind(1, certName.wireEncode(), SQLITE_TRANSIENT);
+  return (statement.step() == SQLITE_ROW);
+}
+
+void
+PibSqlite3::addCertificate(const IdentityCertificate& certificate)
+{
+  const Name& certName = certificate.getName();
+  const Name& keyName = certificate.getPublicKeyName();
+
+  name::Component keyId = keyName.get(-1);
+  Name identityName = keyName.getPrefix(-1);
+
+  // ensure key exists
+  addKey(identityName, keyId, certificate.getPublicKeyInfo());
+
+  Sqlite3Statement statement(m_database,
+                             "INSERT INTO certificates "
+                             "(key_id, certificate_name, certificate_data) "
+                             "VALUES ((SELECT id FROM keys WHERE key_name=?), ?, ?)");
+  statement.bind(1, keyName.wireEncode(), SQLITE_TRANSIENT);
+  statement.bind(2, certName.wireEncode(), SQLITE_TRANSIENT);
+  statement.bind(3, certificate.wireEncode(), SQLITE_STATIC);
+  statement.step();
+}
+
+void
+PibSqlite3::removeCertificate(const Name& certName)
+{
+  Sqlite3Statement statement(m_database, "DELETE FROM certificates WHERE certificate_name=?");
+  statement.bind(1, certName.wireEncode(), SQLITE_TRANSIENT);
+  statement.step();
+}
+
+IdentityCertificate
+PibSqlite3::getCertificate(const Name& certName) const
+{
+  Sqlite3Statement statement(m_database,
+                             "SELECT certificate_data FROM certificates WHERE certificate_name=?");
+  statement.bind(1, certName.wireEncode(), SQLITE_TRANSIENT);
+
+  if (statement.step() == SQLITE_ROW)
+    return IdentityCertificate(statement.getBlock(0));
+  else
+    throw Pib::Error("Certificate does not exit");
+}
+
+std::set<Name>
+PibSqlite3::getCertificatesOfKey(const Name& identity, const name::Component& keyId) const
+{
+  std::set<Name> certNames;
+
+  Name keyName = getKeyName(identity, keyId);
+
+  Sqlite3Statement statement(m_database,
+                             "SELECT certificate_name "
+                             "FROM certificates JOIN keys ON certificates.key_id=keys.id "
+                             "WHERE keys.key_name=?");
+  statement.bind(1, keyName.wireEncode(), SQLITE_TRANSIENT);
+
+  while (statement.step() == SQLITE_ROW)
+    certNames.insert(Name(statement.getBlock(0)));
+
+  return certNames;
+}
+
+void
+PibSqlite3::setDefaultCertificateOfKey(const Name& identity, const name::Component& keyId,
+                                       const Name& certName)
+{
+  if (!hasCertificate(certName)) {
+    throw Pib::Error("Certificate does not exist");
+  }
+
+  Sqlite3Statement statement(m_database,
+                             "UPDATE certificates SET is_default=1 WHERE certificate_name=?");
+  statement.bind(1, certName.wireEncode(), SQLITE_TRANSIENT);
+  statement.step();
+}
+
+IdentityCertificate
+PibSqlite3::getDefaultCertificateOfKey(const Name& identity, const name::Component& keyId) const
+{
+  Name keyName = getKeyName(identity, keyId);
+
+  Sqlite3Statement statement(m_database,
+                             "SELECT certificate_data "
+                             "FROM certificates JOIN keys ON certificates.key_id=keys.id "
+                             "WHERE certificates.is_default=1 AND keys.key_name=?");
+  statement.bind(1, keyName.wireEncode(), SQLITE_TRANSIENT);
+
+  if (statement.step() == SQLITE_ROW)
+    return IdentityCertificate(statement.getBlock(0));
+  else
+    throw Pib::Error("Certificate does not exit");
+}
+
+} // namespace security
+} // namespace ndn