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