From d489f11e52084ce4ca6a25938cb25682a9483d41 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 1 Sep 2019 14:07:58 +0200 Subject: [PATCH] Store encrypted passwords with connections. Closes #22 as encrypted password is now deleted as part of the connection record. --- core/KeyStrengthener.cpp | 61 ++++++ core/KeyStrengthener.h | 29 +++ core/PasswordManager.cpp | 259 ++++++++--------------- core/PasswordManager.h | 55 +---- core/core.pro | 2 + pglab/ConnectionController.cpp | 68 ++---- pglab/ConnectionController.h | 7 +- pglab/ConnectionListModel.cpp | 367 ++------------------------------- pglab/ConnectionListModel.h | 61 +----- pglablib/ConnectionConfig.cpp | 30 ++- pglablib/ConnectionConfig.h | 8 +- 11 files changed, 252 insertions(+), 695 deletions(-) create mode 100644 core/KeyStrengthener.cpp create mode 100644 core/KeyStrengthener.h diff --git a/core/KeyStrengthener.cpp b/core/KeyStrengthener.cpp new file mode 100644 index 0000000..2aa18a7 --- /dev/null +++ b/core/KeyStrengthener.cpp @@ -0,0 +1,61 @@ +#include "KeyStrengthener.h" +#include +#include +#include +#include +#include + +KeyStrengthener::KeyStrengthener(std::unique_ptr hasher, Botan::secure_vector salt, size_t keysize) + : m_hasher (std::move(hasher)) + , m_salt (std::move(salt)) + , m_keySize(keysize) +{} + +KeyStrengthener::KeyStrengthener(KeyStrengthener &&rhs) + : m_hasher (std::move(rhs.m_hasher)) + , m_salt (std::move(rhs.m_salt)) + , m_keySize(rhs.m_keySize) +{} + +KeyStrengthener &KeyStrengthener::operator=(KeyStrengthener &&rhs) +{ + if (&rhs != this) { + m_hasher = std::move(rhs.m_hasher); + m_salt = std::move(rhs.m_salt); + m_keySize = rhs.m_keySize; + } + return *this; +} + +Botan::secure_vector KeyStrengthener::derive(const std::string &passphrase) +{ + Botan::secure_vector master_key(m_keySize); + m_hasher->derive_key(master_key.data(), master_key.size(), passphrase.c_str(), passphrase.length(), m_salt.data(), m_salt.size()); + + return master_key; +} + +void KeyStrengthener::saveParams(QSqlDatabase &db, const QString &table_name) +{ + auto sc = dynamic_cast(m_hasher.get()); + size_t i1 = sc->N(); + size_t i2 = sc->r(); + size_t i3 = sc->p(); + + auto salt_str = QString::fromUtf8(Botan::base64_encode(m_salt).c_str()); + // SAVE parameters in database + QSqlQuery insert_statement(db); + insert_statement.prepare("INSERT OR REPLACE INTO " + table_name + "(id, algo, i1, i2, i3, ks, salt) " + + "VALUES(:id, :algo, :i1, :i2, :i3, :ks, :salt)"); + insert_statement.bindValue(":id", 1); + insert_statement.bindValue(":algo", "Scrypt"); + insert_statement.bindValue(":i1", i1); + insert_statement.bindValue(":i2", i2); + insert_statement.bindValue(":i3", i3); + insert_statement.bindValue(":ks", m_keySize); + insert_statement.bindValue(":salt", salt_str); + if (!insert_statement.exec()) { + throw std::runtime_error("PasswordManager::KeyStrengthener::saveParams failed"); +// auto err = insert_statement.lastError(); + } +} diff --git a/core/KeyStrengthener.h b/core/KeyStrengthener.h new file mode 100644 index 0000000..5618167 --- /dev/null +++ b/core/KeyStrengthener.h @@ -0,0 +1,29 @@ +#ifndef KEYSTRENGTHENER_H +#define KEYSTRENGTHENER_H + +#include +#include +#include +#include + +class KeyStrengthener { +public: + KeyStrengthener() = default; + KeyStrengthener(std::unique_ptr hasher, Botan::secure_vector salt, size_t keysize); + + KeyStrengthener(const KeyStrengthener&) = delete; + KeyStrengthener& operator=(const KeyStrengthener &) = delete; + + KeyStrengthener(KeyStrengthener &&rhs); + + KeyStrengthener& operator=(KeyStrengthener &&rhs); + + Botan::secure_vector derive(const std::string &passphrase); + void saveParams(QSqlDatabase &db, const QString &table_name); +private: + std::unique_ptr m_hasher; + Botan::secure_vector m_salt; + size_t m_keySize; +}; + +#endif // KEYSTRENGTHENER_H diff --git a/core/PasswordManager.cpp b/core/PasswordManager.cpp index b57b972..0a52265 100644 --- a/core/PasswordManager.cpp +++ b/core/PasswordManager.cpp @@ -7,11 +7,11 @@ #include #include #include -//#include -#include - -//#include #include +#include +#include +#include +#include #include namespace { @@ -26,141 +26,59 @@ namespace { }; - class QPSK_Database : public Botan::Encrypted_PSK_Database +} + +using namespace Botan; + +class PasswordCryptoEngine { +public: + PasswordCryptoEngine(const secure_vector& master_key) { - public: - /** - * @param master_key specifies the master key used to encrypt all - * keys and value. It can be of any length, but should be at least 256 bits. - * - * Subkeys for the cryptographic algorithms used are derived from this - * master key. No key stretching is performed; if encrypting a PSK database - * using a password, it is recommended to use PBKDF2 to derive the database - * master key. - */ - QPSK_Database(const Botan::secure_vector& master_key, QSqlDatabase &db, const QString &table_name) - : Encrypted_PSK_Database(master_key) - , m_db(db) - , m_tableName(table_name) - { - QSqlQuery q_create_table(m_db); - q_create_table.prepare("CREATE TABLE IF NOT EXISTS " + table_name + - "(psk_name TEXT PRIMARY KEY, psk_value TEXT)"); - if (!q_create_table.exec()) { - auto err = q_create_table.lastError(); - throw SqlException(err); - } - } - - protected: - /// Save a encrypted (name.value) pair to the database. Both will be base64 encoded strings. - virtual void kv_set(const std::string& index, const std::string& value) override - { - QSqlQuery q(m_db); - q.prepare("insert or replace into " + m_tableName + " values(:name, :value)"); - q.bindValue(":name", QString::fromUtf8(index.c_str())); - q.bindValue(":value", QString::fromUtf8(value.c_str())); - if (!q.exec()) { - auto err = q.lastError(); - throw SqlException(err); - } - } - - /// Get a value previously saved with set_raw_value. Should return an empty - /// string if index is not found. - virtual std::string kv_get(const std::string& index) const override - { - QSqlQuery q(m_db); - q.prepare("SELECT psk_value FROM " + m_tableName + - " WHERE psk_name = :name"); - q.bindValue(":name", QString::fromUtf8(index.c_str())); - if (q.exec()) { - if (q.next()) { - return q.value(0).toString().toUtf8().data(); - } - } - else { - auto err = q.lastError(); - throw SqlException(err); - } - return std::string(); - } - - /// Remove an index - virtual void kv_del(const std::string& index) override - { - QSqlQuery q(m_db); - q.prepare("DELETE FROM " + m_tableName + " WHERE psk_name=:name"); - q.bindValue(":name", QString::fromUtf8(index.c_str())); - if (!q.exec()) { - auto err = q.lastError(); - throw SqlException(err); - } - } - - /// Return all indexes in the table. - virtual std::set kv_get_all() const override - { - QSqlQuery q(m_db); - q.prepare("SELECT psk_name FROM " + m_tableName); - - std::set result; - if (q.exec()) { - while (q.next()) { - result.insert(q.value(0).toString().toUtf8().data()); - } - } - else { - auto err = q.lastError(); - throw SqlException(err); - } - return result; - } - private: - QSqlDatabase &m_db; - QString m_tableName; - }; - - -} - - -Botan::secure_vector PasswordManager::KeyStrengthener::derive(const std::string &passphrase) -{ - Botan::secure_vector master_key(m_keySize); - m_hasher->derive_key(master_key.data(), master_key.size(), passphrase.c_str(), passphrase.length(), m_salt.data(), m_salt.size()); - - return master_key; -} - -void PasswordManager::KeyStrengthener::saveParams(QSqlDatabase &db, const QString &table_name) -{ - auto sc = dynamic_cast(m_hasher.get()); - size_t i1 = sc->N(); - size_t i2 = sc->r(); - size_t i3 = sc->p(); - - - auto salt_str = QString::fromUtf8(Botan::base64_encode(m_salt).c_str()); - // SAVE parameters in database - QSqlQuery insert_statement(db); - insert_statement.prepare("INSERT OR REPLACE INTO " + table_name + "(id, algo, i1, i2, i3, ks, salt) " - + "VALUES(:id, :algo, :i1, :i2, :i3, :ks, :salt)"); - insert_statement.bindValue(":id", 1); - insert_statement.bindValue(":algo", "Scrypt"); - insert_statement.bindValue(":i1", i1); - insert_statement.bindValue(":i2", i2); - insert_statement.bindValue(":i3", i3); - insert_statement.bindValue(":ks", m_keySize); - insert_statement.bindValue(":salt", salt_str); - if (!insert_statement.exec()) { - //throw std::runtime_error("PasswordManager::KeyStrengthener::saveParams failed"); - auto err = insert_statement.lastError(); - throw SqlException(err); + m_cipher = BlockCipher::create_or_throw("AES-256"); + m_hmac = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)"); + m_hmac->set_key(master_key); + m_cipher->set_key(m_hmac->process("wrap")); + m_hmac->set_key(m_hmac->process("hmac")); } -} + std::string set(const std::string& name, const uint8_t val[], size_t len) const + { + /* + * Both as a basic precaution wrt key seperation, and specifically to prevent + * cut-and-paste attacks against the database, each PSK is encrypted with a + * distinct key which is derived by hashing the wrapped key name with HMAC. + */ + const std::vector wrapped_name = + nist_key_wrap_padded(cast_char_ptr_to_uint8(name.data()), + name.size(), + *m_cipher); + + std::unique_ptr wrap_cipher(m_cipher->clone()); + wrap_cipher->set_key(m_hmac->process(wrapped_name)); + const std::vector wrapped_key = nist_key_wrap_padded(val, len, *wrap_cipher); + + return base64_encode(wrapped_key); + } + + secure_vector get(const std::string& name, const std::string &wrapped_key) const + { + const std::vector wrapped_name = + nist_key_wrap_padded(cast_char_ptr_to_uint8(name.data()), + name.size(), + *m_cipher); + + const secure_vector val = base64_decode(wrapped_key); + + std::unique_ptr wrap_cipher(m_cipher->clone()); + wrap_cipher->set_key(m_hmac->process(wrapped_name)); + + return nist_key_unwrap_padded(val.data(), val.size(), *wrap_cipher); + } +private: + std::unique_ptr m_cipher; + std::unique_ptr m_hmac; +}; // ------------------------- @@ -173,18 +91,27 @@ bool PasswordManager::initialized(QSqlDatabase& db) return isPskStoreInitialized(db); } +std::tuple, Botan::secure_vector> +PasswordManager::deriveKey(KeyStrengthener &ks, QString passphrase) +{ + auto master_key = ks.derive(passphrase.toUtf8().data()); + + std::unique_ptr hash3(Botan::HashFunction::create("SHA-3")); + hash3->update(master_key); + auto mkh = hash3->final(); + return { master_key, mkh }; +} + bool PasswordManager::createDatabase(QSqlDatabase &db, QString passphrase) { + m_cryptoEngine.reset(); if (!isPskStoreInitialized(db)) { initializeNewPskStore(db); auto ks = createKeyStrengthener(); ks.saveParams(db, m_secretAlgoTableName); - auto master_key = ks.derive(passphrase.toUtf8().data()); - - std::unique_ptr hash3(Botan::HashFunction::create("SHA-3")); - hash3->update(master_key); - auto mkh = QString::fromUtf8(Botan::base64_encode(hash3->final()).c_str()); + auto [master_key, mkh_bin] = deriveKey(ks, passphrase); + auto mkh = QString::fromUtf8(Botan::base64_encode(mkh_bin).c_str()); QSqlQuery q_ins_hash(db); q_ins_hash.prepare("INSERT INTO " + m_secretHashTableName + "(id, hash) VALUES(:id, :hash)"); @@ -196,7 +123,7 @@ bool PasswordManager::createDatabase(QSqlDatabase &db, QString passphrase) throw SqlException(err); } - m_pskDatabase = std::make_unique(master_key, db, m_passwordTableName); + m_cryptoEngine = std::make_unique(master_key); return true; } return false; @@ -204,20 +131,17 @@ bool PasswordManager::createDatabase(QSqlDatabase &db, QString passphrase) bool PasswordManager::openDatabase(QSqlDatabase &db, QString passphrase) { + m_cryptoEngine.reset(); if (isPskStoreInitialized(db)) { auto ks = getKeyStrengthener(db); - - auto master_key = ks.derive(passphrase.toUtf8().data()); - std::unique_ptr hash3(Botan::HashFunction::create("SHA-3")); - hash3->update(master_key); - auto mkh_bin = hash3->final(); + auto [master_key, mkh_bin] = deriveKey(ks, passphrase); QSqlQuery q("SELECT hash FROM " + m_secretHashTableName + " WHERE id=1", db); if (q.next()) { auto hash_b64 = q.value(0).toString().toUtf8(); auto hash_bin = Botan::base64_decode(hash_b64.data(), static_cast(hash_b64.size())); if (hash_bin == mkh_bin) { - m_pskDatabase = std::make_unique(master_key, db, m_passwordTableName); + m_cryptoEngine = std::make_unique(master_key); return true; } } @@ -227,52 +151,35 @@ bool PasswordManager::openDatabase(QSqlDatabase &db, QString passphrase) void PasswordManager::closeDatabase() { - m_pskDatabase.reset(); + m_cryptoEngine.reset(); } bool PasswordManager::locked() const { - return m_pskDatabase == nullptr; + return m_cryptoEngine == nullptr; } -void PasswordManager::set(const std::string &id, const std::string &passwd) +std::string PasswordManager::encrypt(const std::string &name, const std::string &passwd) { - if (m_pskDatabase) { - m_pskDatabase->set_str(id, passwd); + if (m_cryptoEngine) { + return m_cryptoEngine->set(name, reinterpret_cast(passwd.data()), passwd.length()); } else { throw PasswordManagerLockedException(); } } -bool PasswordManager::get(const std::string &id, std::string &password) +std::string PasswordManager::decrypt(const std::string &id, const std::string &encpwd) { - if (m_pskDatabase) { - try { - password = m_pskDatabase->get_str(id); - return true; - } - catch (const Botan::Invalid_Argument &) { - // not present - return false; - } + if (m_cryptoEngine) { + secure_vector decoded = m_cryptoEngine->get(id, encpwd); + return std::string(reinterpret_cast(decoded.data()), decoded.size()); } else { throw PasswordManagerLockedException(); } } -void PasswordManager::remove(const std::string &id) -{ - if (m_pskDatabase) { - m_pskDatabase->remove(id); - } - else { - throw PasswordManagerLockedException(); - } -} - - void PasswordManager::initializeNewPskStore(QSqlDatabase &db) { // // Create tables @@ -347,7 +254,7 @@ bool PasswordManager::isPskStoreInitialized(QSqlDatabase& db) return true; } -PasswordManager::KeyStrengthener PasswordManager::getKeyStrengthener(QSqlDatabase &db) +KeyStrengthener PasswordManager::getKeyStrengthener(QSqlDatabase &db) { QSqlQuery query("SELECT algo, i1, i2, i3, ks, salt FROM " + m_secretAlgoTableName + " WHERE id=1", db); if (query.next()) { @@ -370,10 +277,8 @@ PasswordManager::KeyStrengthener PasswordManager::getKeyStrengthener(QSqlDatabas } } -PasswordManager::KeyStrengthener PasswordManager::createKeyStrengthener() +KeyStrengthener PasswordManager::createKeyStrengthener() { -// std::unique_ptr pwh; - size_t key_size = 64; Botan::secure_vector salt(key_size); Botan::AutoSeeded_RNG rng; diff --git a/core/PasswordManager.h b/core/PasswordManager.h index 62635df..291ef7e 100644 --- a/core/PasswordManager.h +++ b/core/PasswordManager.h @@ -2,23 +2,19 @@ #define PASSWORDMANAGER_H #include "Expected.h" +#include "KeyStrengthener.h" #include #include #include +#include #include #include - - -//#include -//#include - #include namespace Botan { class Encrypted_PSK_Database; - //class Sqlite3_Database; class PasswordHash; } @@ -33,6 +29,7 @@ public: using PasswordManagerException::PasswordManagerException; }; +class PasswordCryptoEngine; class PasswordManager { public: @@ -57,58 +54,24 @@ public: void closeDatabase(); bool locked() const; - void set(const std::string &id, const std::string &passwd); - bool get(const std::string &id, std::string &password); - void remove(const std::string &id); + std::string encrypt(const std::string &id, const std::string &passwd); + std::string decrypt(const std::string &id, const std::string &encpwd); +// void remove(const std::string &id); private: QString m_passwordTableName = "psk_passwd"; QString m_secretAlgoTableName = "psk_masterkey_algo"; QString m_secretHashTableName = "psk_masterkey_hash"; - std::unique_ptr m_pskDatabase; + std::unique_ptr m_cryptoEngine; bool isPskStoreInitialized(QSqlDatabase& db); void initializeNewPskStore(QSqlDatabase &db); - class KeyStrengthener { - public: - KeyStrengthener() = default; - KeyStrengthener(std::unique_ptr hasher, Botan::secure_vector salt, size_t keysize) - : m_hasher (std::move(hasher)) - , m_salt (std::move(salt)) - , m_keySize(keysize) - {} - - KeyStrengthener(const KeyStrengthener&) = delete; - KeyStrengthener& operator=(const KeyStrengthener &) = delete; - - KeyStrengthener(KeyStrengthener &&rhs) - : m_hasher (std::move(rhs.m_hasher)) - , m_salt (std::move(rhs.m_salt)) - , m_keySize(rhs.m_keySize) - {} - - KeyStrengthener& operator=(KeyStrengthener &&rhs) - { - if (&rhs != this) { - m_hasher = std::move(rhs.m_hasher); - m_salt = std::move(rhs.m_salt); - m_keySize = rhs.m_keySize; - } - return *this; - } - - Botan::secure_vector derive(const std::string &passphrase); - void saveParams(QSqlDatabase &db, const QString &table_name); - private: - std::unique_ptr m_hasher; - Botan::secure_vector m_salt; - size_t m_keySize; - }; - /// Get PasswordHash from parameters in database KeyStrengthener getKeyStrengthener(QSqlDatabase &db); KeyStrengthener createKeyStrengthener(); + std::tuple, Botan::secure_vector> + deriveKey(KeyStrengthener &ks, QString passphrase); }; diff --git a/core/core.pro b/core/core.pro index d0851a8..ab3d990 100644 --- a/core/core.pro +++ b/core/core.pro @@ -21,6 +21,7 @@ error( "Couldn't find the common.pri file!" ) #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += my_boost_assert_handler.cpp \ + KeyStrengthener.cpp \ SqlLexer.cpp \ PasswordManager.cpp \ CsvWriter.cpp \ @@ -36,6 +37,7 @@ SOURCES += my_boost_assert_handler.cpp \ SqlAstExpression.cpp HEADERS += PasswordManager.h \ + KeyStrengthener.h \ SqlLexer.h \ ScopeGuard.h \ CsvWriter.h \ diff --git a/pglab/ConnectionController.cpp b/pglab/ConnectionController.cpp index 939f512..92faa22 100644 --- a/pglab/ConnectionController.cpp +++ b/pglab/ConnectionController.cpp @@ -25,10 +25,6 @@ ConnectionController::~ConnectionController() void ConnectionController::init() { - //std::string dbfilename = QDir::toNativeSeparators(GetUserConfigDatabaseName()).toUtf8().data(); - //m_userConfigDatabase = std::make_shared(dbfilename); - - m_passwordManager = std::make_shared(); m_connectionTreeModel = new ConnectionTreeModel(this, m_masterController->userConfigDatabase()); @@ -36,7 +32,6 @@ void ConnectionController::init() m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr); m_connectionManagerWindow->show(); - } void ConnectionController::showConnectionManager() @@ -44,30 +39,9 @@ void ConnectionController::showConnectionManager() m_connectionManagerWindow->show(); } -namespace { - - ConnectionConfig* getConfigFromModelIndex(QModelIndex index) - { - if (!index.isValid()) - return nullptr; - auto node = static_cast(index.internalPointer()); - return dynamic_cast(node); - } - - ConnectionGroup* getGroupFromModelIndex(QModelIndex index) - { - if (!index.isValid()) - return nullptr; - auto node = static_cast(index.internalPointer()); - return dynamic_cast(node); - } - -} - - void ConnectionController::openSqlWindowForConnection(QModelIndex index) { - auto config = getConfigFromModelIndex(index); + auto config = ConnectionTreeModel::getConfigFromModelIndex(index); if (config) { if (retrieveConnectionPassword(*config)) { @@ -86,7 +60,7 @@ void ConnectionController::openSqlWindowForConnection(QModelIndex index) void ConnectionController::openBackupDlgForConnection(QModelIndex index) { - auto config = getConfigFromModelIndex(index); + auto config = ConnectionTreeModel::getConfigFromModelIndex(index); if (config) { if (retrieveConnectionPassword(*config)) { m_connectionTreeModel->save(*config); @@ -107,7 +81,7 @@ void ConnectionController::createConnection() void ConnectionController::editConnection(QModelIndex index) { - auto config = getConfigFromModelIndex(index); + auto config = ConnectionTreeModel::getConfigFromModelIndex(index); if (config) { ConnectionConfigurationWidget::editExistingInWindow(this, *config); } @@ -129,7 +103,7 @@ void ConnectionController::addGroup() void ConnectionController::removeGroup(QModelIndex index) { - auto group = getGroupFromModelIndex(index); + auto group = ConnectionTreeModel::getGroupFromModelIndex(index); if (group) { auto btn = QMessageBox::question(nullptr, tr("Connection group"), tr("Remove the selected group and all connections contained in the group?"), @@ -141,9 +115,14 @@ void ConnectionController::removeGroup(QModelIndex index) } } +std::shared_ptr ConnectionController::passwordManager() +{ + return m_passwordManager; +} + void ConnectionController::openServerWindowForConnection(QModelIndex index) { - auto config = getConfigFromModelIndex(index); + auto config = ConnectionTreeModel::getConfigFromModelIndex(index); if (config) { if (retrieveConnectionPassword(*config)) { m_connectionTreeModel->save(*config); @@ -155,16 +134,12 @@ void ConnectionController::openServerWindowForConnection(QModelIndex index) } } - bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc) { - auto pw_state = cc.passwordState(); - if (pw_state == PasswordState::NotNeeded) { - return true; - } - else if (pw_state == PasswordState::SavedPasswordManager) { + auto enc_pwd = cc.encodedPassword(); + if (!enc_pwd.empty()) { std::string pw; - bool result = getPasswordFromPskdb(getPskId(cc), pw); + bool result = decodePassword(getPskId(cc), cc.encodedPassword(), pw);// getPasswordFromPskdb(getPskId(cc), pw); if (result) { cc.setPassword(pw); return true; @@ -182,30 +157,31 @@ bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc) std::string password = dlg->password().toUtf8().data(); cc.setPassword(password); if (dlg->saveChecked()) { - storePasswordInPskdb(getPskId(cc), password); - cc.setPasswordState(PasswordState::SavedPasswordManager); + std::string encoded_pw; + if (encodePassword(getPskId(cc), password, encoded_pw)) { + cc.setEncodedPassword(encoded_pw); + } } return true; } return false; } - -bool ConnectionController::getPasswordFromPskdb(const std::string &password_id, std::string &password) +bool ConnectionController::decodePassword(const std::string &password_id, const std::string &enc_password, std::string &password) { if (!UnlockPasswordManagerIfNeeded()) return false; - return m_passwordManager->get(password_id, password); + password = m_passwordManager->decrypt(password_id, enc_password); + return true; } - -bool ConnectionController::storePasswordInPskdb(const std::string &password_id, const std::string password) +bool ConnectionController::encodePassword(const std::string &password_id, const std::string &password, std::string &enc_password) { if (!UnlockPasswordManagerIfNeeded()) return false; - m_passwordManager->set(password_id, password); + enc_password = m_passwordManager->encrypt(password_id, password); return true; } diff --git a/pglab/ConnectionController.h b/pglab/ConnectionController.h index ffd51c9..2f942d4 100644 --- a/pglab/ConnectionController.h +++ b/pglab/ConnectionController.h @@ -36,6 +36,8 @@ public: void editConnection(QModelIndex index); void addGroup(); void removeGroup(QModelIndex index); + + std::shared_ptr passwordManager(); private: MasterController *m_masterController; ConnectionList *m_connectionList = nullptr; @@ -52,9 +54,8 @@ private: */ bool retrieveConnectionPassword(ConnectionConfig &cc); - bool getPasswordFromPskdb(const std::string &password_id, std::string &password); - - bool storePasswordInPskdb(const std::string &password_id, const std::string password); + bool decodePassword(const std::string &password_id, const std::string &enc_password, std::string &password); + bool encodePassword(const std::string &password_id, const std::string &password, std::string &enc_password); bool UnlockPasswordManagerIfNeeded(); diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index 8c870ce..ede4713 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -36,7 +36,7 @@ CREATE TABLE IF NOT EXISTS connection ( sslkey TEXT NOT NULL, sslrootcert TEXT NOT NULL, sslcrl TEXT NOT NULL, - passwordstate INTEGER NOT NULL + password TEXT NOT NULL );)__"; @@ -44,7 +44,7 @@ CREATE TABLE IF NOT EXISTS connection ( const char * const q_insert_or_replace_into_connection = R"__(INSERT OR REPLACE INTO connection VALUES (:uuid, :name, :conngroup_id, :host, :hostaddr, :port, :user, :dbname, - :sslmode, :sslcert, :sslkey, :sslrootcert, :sslcrl, :passwordstate); + :sslmode, :sslcert, :sslkey, :sslrootcert, :sslcrl, :password); )__" ; std::tuple InitConnectionTables(QSqlDatabase &db) @@ -80,7 +80,7 @@ R"__(INSERT OR REPLACE INTO connection q.bindValue(":sslkey", stdStrToQ(cc.sslKey())); q.bindValue(":sslrootcert", stdStrToQ(cc.sslRootCert())); q.bindValue(":sslcrl", stdStrToQ(cc.sslCrl())); - q.bindValue(":passwordstate", static_cast(cc.passwordState())); + q.bindValue(":password", stdStrToQ(cc.encodedPassword())); if (!q.exec()) { return q.lastError(); @@ -88,311 +88,8 @@ R"__(INSERT OR REPLACE INTO connection return {}; } - /** Saves a connection configuration. - - Before calling this you may want to call beginGroup. - */ -// void SaveConnectionConfig(QSettings &settings, const ConnectionConfig &cc) -// { -// settings.setValue("name", stdStrToQ(cc.name())); -// settings.setValue("host", stdStrToQ(cc.host())); -// settings.setValue("hostaddr", stdStrToQ(cc.hostAddr())); -// settings.setValue("port", cc.port()); -// settings.setValue("user", stdStrToQ(cc.user())); -// //settings.setValue("password", stdStrToQ(cc.password())); -// settings.setValue("dbname", stdStrToQ(cc.dbname())); -// settings.setValue("sslmode", static_cast(cc.sslMode())); -// settings.setValue("sslcert", stdStrToQ(cc.sslCert())); -// settings.setValue("sslkey", stdStrToQ(cc.sslKey())); -// settings.setValue("sslrootcert", stdStrToQ(cc.sslRootCert())); -// settings.setValue("sslcrl", stdStrToQ(cc.sslCrl())); -// settings.setValue("passwordState", static_cast(cc.passwordState())); -// } - - - -// template -// bool in_range(T value) -// { -// return value >= std::numeric_limits::min() && value <= std::numeric_limits::max(); -// } - -// void LoadConnectionConfig(QSettings &settings, ConnectionConfig &cc) -// { -// cc.setName(qvarToStdStr(settings.value("name"))); -// cc.setHost(qvarToStdStr(settings.value("host"))); -// cc.setHostAddr(qvarToStdStr(settings.value("hostaddr"))); -// int p = settings.value("port", 5432).toInt(); -// if (!in_range(p)) { -// p = 0; // let the user re-enter a valid value -// } - -// cc.setPort(static_cast(p)); -// cc.setUser(qvarToStdStr(settings.value("user"))); - -// //cc.setPassword(qvarToStdStr(settings.value("password"))); - -// cc.setDbname(qvarToStdStr(settings.value("dbname"))); -// cc.setSslMode(static_cast(settings.value("sslmode").toInt())); -// cc.setSslCert(qvarToStdStr(settings.value("sslcert"))); -// cc.setSslKey(qvarToStdStr(settings.value("sslkey"))); -// cc.setSslRootCert(qvarToStdStr(settings.value("sslrootcert"))); -// cc.setSslCrl(qvarToStdStr(settings.value("sslcrl"))); - -// PasswordState pwstate; -// QVariant v = settings.value("passwordState"); -// if (v.isNull()) pwstate = PasswordState::NotStored; -// else pwstate = static_cast(v.toInt()); -// cc.setPasswordState(pwstate); -// } - - } // end of unnamed namespace - -#if false -ConnectionListModel::ConnectionListModel(QObject *parent) - : QAbstractListModel(parent) -{ -} - -ConnectionListModel::~ConnectionListModel() = default; - -int ConnectionListModel::rowCount(const QModelIndex &parent) const -{ - int result = 0; - if (parent == QModelIndex()) { - result = m_connections.size(); - } - return result; -} - -int ConnectionListModel::columnCount(const QModelIndex &/*parent*/) const -{ - return ColCount; -} - -QVariant ConnectionListModel::data(const QModelIndex &index, int role) const -{ - QVariant result; - if (role == Qt::DisplayRole || role == Qt::EditRole) { - int row = index.row(); - int col = index.column(); - const ConnectionConfig& cfg = m_connections.at(row); - switch (col) { - case Description: - result = makeLongDescription(cfg); - break; - case Name: - result = stdStrToQ(cfg.name()); - break; - case Host: - result = stdStrToQ(cfg.host()); - break; - case Port: - result = cfg.port(); - break; - case User: - result = stdStrToQ(cfg.user()); - break; - case Password: - result = stdStrToQ(cfg.password()); - break; - case DbName: - result = stdStrToQ(cfg.dbname()); - break; - } - } - - return result; -} - -bool ConnectionListModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - bool result = false; - if (role == Qt::EditRole) { - int row = index.row(); - int col = index.column(); -// auto& elem = m_connections.at(row); -// elem.m_dirty = true; -// ConnectionConfig& cfg = elem.m_config; - ConnectionConfig& cfg = m_connections[row]; - if (col > 0) { - result = true; - } - switch (col) { - case Description: - break; - case Name: - cfg.setName( qStrToStd(value.toString()) ); - break; - case Host: - cfg.setHost( qStrToStd(value.toString()) ); - break; - case Port: - cfg.setPort( value.toInt() ); - break; - case User: - cfg.setUser( qStrToStd(value.toString()) ); - break; - case Password: - cfg.setPassword( qStrToStd(value.toString()) ); - break; - case DbName: - cfg.setDbname( qStrToStd(value.toString()) ); - break; - } - } - if (result) { - emit dataChanged(index, index); - } - return result; -} - -Qt::ItemFlags ConnectionListModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags result; - int row = index.row(); - if (row >= 0 && row < m_connections.size()) { - result = Qt::ItemIsSelectable | Qt::ItemIsEnabled; - if (index.column() != Description) - result |= Qt::ItemIsEditable; - } - return result; -} - - -QString ConnectionListModel::makeLongDescription(const ConnectionConfig &cfg) -{ - std::string result(cfg.name()); - result += " ("; - result += cfg.user(); - result += "@"; - result += cfg.host(); - result += ":"; - result += std::to_string(cfg.port()); - result += "/"; - result += cfg.dbname(); - result += ")"; - return stdStrToQ(result); -} - -bool ConnectionListModel::removeRows(int row, int count, const QModelIndex &parent) -{ - bool result = false; - - if (row >= 0 && row < m_connections.size()) { - - beginRemoveRows(parent, row, row + count -1); - SCOPE_EXIT { endRemoveRows(); }; - - QString file_name = iniFileName(); - QSettings settings(file_name, QSettings::IniFormat); - for (int idx = 0; idx < count; ++idx) { - auto&& cc = m_connections[idx+row]; - settings.remove(cc.uuid().toString()); - } - settings.sync(); - m_connections.remove(row, count); - result = true; - } - return result; -} - -//void ConnectionListModel::newItem() -//{ -//// int i = m_connections->createNew(); -//// auto idx = createIndex(i, 0); -//// emit dataChanged(idx, idx); -//} - -Expected ConnectionListModel::get(int row) -{ - if (row < m_connections.size()) { - return m_connections.at(row); - } - return Expected::fromException(std::out_of_range("Invalid row")); -} - -void ConnectionListModel::load() -{ - QString file_name = iniFileName(); - QSettings settings(file_name, QSettings::IniFormat); - auto groups = settings.childGroups(); - for (auto&& grp : groups) { - if (grp == "c_IniGroupSecurity") { - // Read security settings - - } else { - QUuid uuid(grp); - if ( ! uuid.isNull() ) { - settings.beginGroup(grp); - SCOPE_EXIT { settings.endGroup(); }; - - ConnectionConfig cc; - cc.setUuid(uuid); - LoadConnectionConfig(settings, cc); - m_connections.push_back(cc); - } - } - } -} - -void ConnectionListModel::save() -{ - QString file_name = iniFileName(); - QSettings settings(file_name, QSettings::IniFormat); - for (auto& e : m_connections) { - settings.beginGroup(e.uuid().toString()); - SCOPE_EXIT { settings.endGroup(); }; - - SaveConnectionConfig(settings, e); - e.clean(); - } - settings.sync(); -} - -void ConnectionListModel::save(int index) -{ - auto& e = m_connections[index]; - if (e.dirty()) { - QString file_name = iniFileName(); - QSettings settings(file_name, QSettings::IniFormat); - settings.beginGroup(e.uuid().toString()); - SaveConnectionConfig(settings, e); - e.clean(); - settings.sync(); - } -} - -void ConnectionListModel::save(const ConnectionConfig &cc) -{ - auto find_res = std::find(m_connections.begin(), m_connections.end(), cc.uuid()); - int i; - if (find_res == m_connections.end()) { - m_connections.push_back(cc); - i = m_connections.size() - 1; - } - else { - *find_res = cc; - i = find_res - m_connections.begin(); - } - emit dataChanged(createIndex(i, 0), createIndex(i, ColCount-1)); - save(i); -} - -QString ConnectionListModel::iniFileName() -{ - QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - QDir dir(path); - if (!dir.exists()) { - dir.mkpath("."); - } - path += "/connections.ini"; - return path; -} -#endif - ConnectionTreeModel::ConnectionTreeModel(QObject *parent, QSqlDatabase &db) : QAbstractItemModel(parent) , m_db(db) @@ -422,7 +119,7 @@ void ConnectionTreeModel::load() q.prepare("SELECT uuid, cname, conngroup_id, host, hostaddr, port, " " user, dbname, sslmode, sslcert, sslkey, sslrootcert, sslcrl, " - " passwordstate " + " password " "FROM connection ORDER BY conngroup_id, cname;"); if (!q.exec()) { // auto err = q_create_table.lastError(); @@ -443,7 +140,7 @@ void ConnectionTreeModel::load() cc->setSslKey(qvarToStdStr(q.value(10))); cc->setSslRootCert(qvarToStdStr(q.value(11))); cc->setSslCrl(qvarToStdStr(q.value(12))); - cc->setPasswordState(static_cast(q.value(13).toInt())); + cc->setEncodedPassword(qvarToStdStr(q.value(13))); int group_id = q.value(2).toInt(); auto find_res = std::find_if(m_groups.begin(), m_groups.end(), @@ -455,27 +152,6 @@ void ConnectionTreeModel::load() throw std::runtime_error("conngroup missing"); } } - -// auto g1 = std::make_shared(); -// g1->name = "Testing"; - -// for (int i = 1; i < 3; ++i) { -// auto cc = std::make_shared(); -// cc->setUuid(QUuid::createUuid()); -// cc->setName("testconn " + std::to_string(i)); -// g1->add(cc); -// } - -// auto g2 = std::make_shared(); -// g2->name = "Production"; -// for (int i = 1; i < 4; ++i) { -// auto cc = std::make_shared(); -// cc->setUuid(QUuid::createUuid()); -// cc->setName("prodconn " + std::to_string(i)); -// g2->add(cc); -// } - -// m_groups = { g1, g2 }; } QVariant ConnectionTreeModel::data(const QModelIndex &index, int role) const @@ -545,17 +221,6 @@ QModelIndex ConnectionTreeModel::index(int row, int column, const QModelIndex &p node = m_groups[row].get(); } return createIndex(row, column, const_cast(node)); - -// void *p = nullptr; -// if (parent.isValid()) { -// auto cg = static_cast(parent.internalPointer()); -// auto cc = &cg->connections().at(row); -// p = const_cast(cc); -// } -// else { -// p = const_cast(&m_groups.at(row)); -// } -// return createIndex(row, column, p); } QModelIndex ConnectionTreeModel::parent(const QModelIndex &index) const @@ -582,12 +247,6 @@ int ConnectionTreeModel::rowCount(const QModelIndex &parent) const { int result = 0; if (parent.isValid()) { -// if (parent.column() <= 0) { -// result = m_groups[parent.row()].connections().size(); -// } -// else { -// result = 1; -// } auto privdata = static_cast(parent.internalPointer()); if (auto group = dynamic_cast(privdata); group != nullptr) { result = group->connections().size(); @@ -767,6 +426,22 @@ int ConnectionTreeModel::findGroup(int conngroup_id) const return find_res - m_groups.begin(); } +ConnectionConfig *ConnectionTreeModel::getConfigFromModelIndex(QModelIndex index) +{ + if (!index.isValid()) + return nullptr; + auto node = static_cast(index.internalPointer()); + return dynamic_cast(node); +} + +ConnectionGroup *ConnectionTreeModel::getGroupFromModelIndex(QModelIndex index) +{ + if (!index.isValid()) + return nullptr; + auto node = static_cast(index.internalPointer()); + return dynamic_cast(node); +} + std::optional ConnectionTreeModel::saveToDb(const ConnectionConfig &cc) { return SaveConnectionConfig(m_db, cc, cc.parent()->conngroup_id); diff --git a/pglab/ConnectionListModel.h b/pglab/ConnectionListModel.h index ee80dc9..515add6 100644 --- a/pglab/ConnectionListModel.h +++ b/pglab/ConnectionListModel.h @@ -64,6 +64,10 @@ public: std::variant addGroup(QString group_name); std::optional removeGroup(int row); int findGroup(int conngroup_id) const; + + static ConnectionConfig* getConfigFromModelIndex(QModelIndex index); + + static ConnectionGroup* getGroupFromModelIndex(QModelIndex index); private: using Groups = QVector>; @@ -78,61 +82,4 @@ private: std::optional saveToDb(const ConnectionConfig &cc); }; -#if false -/** \brief Model class for the list of connections. - * - * This class also allows for the editing of the list. - */ -class ConnectionListModel : public QAbstractListModel { - Q_OBJECT -public: - enum Columns { - Description, - Name, - Host, - Port, - User, - Password, - DbName, - - ColCount - }; - - ConnectionListModel(QObject *parent); - ConnectionListModel(const ConnectionListModel&) = delete; - ~ConnectionListModel() override; - - // BEGIN Model/View related functions - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; - virtual int columnCount(const QModelIndex &/*parent*/) const override; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - // virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; - // END Model/View related functions - - Expected get(int row); - - void load(); - // Writes all entries to storage - void save(); - // Writes the specified entry to storage - void save(int index); - /** Matches cc to the list by looking at its uuid. - * - * If it is not in the list it is added. If the uuid is in the list that entry is updated. - * In both cases the data is also directly written to long term storage. - */ - void save(const ConnectionConfig &cc); - static QString makeLongDescription(const ConnectionConfig &cfg); -private: - - using ConnectionList = QVector; - ConnectionList m_connections; - - QString iniFileName(); -}; -#endif - #endif // CONNECTIONLISTMODEL_H diff --git a/pglablib/ConnectionConfig.cpp b/pglablib/ConnectionConfig.cpp index 486b7f0..e2fcfd6 100644 --- a/pglablib/ConnectionConfig.cpp +++ b/pglablib/ConnectionConfig.cpp @@ -268,19 +268,6 @@ const char * const * ConnectionConfig::getValues() const return m_values.data(); } -PasswordState ConnectionConfig::passwordState() const -{ - return m_passwordState; -} - -void ConnectionConfig::setPasswordState(PasswordState password_state) -{ - if (m_passwordState != password_state) { - m_dirty = true; - m_passwordState = password_state; - } -} - bool ConnectionConfig::isSameDatabase(const ConnectionConfig &rhs) const { return m_host == rhs.m_host @@ -316,10 +303,21 @@ QString ConnectionConfig::makeLongDescription() const return stdStrToQ(result); } -/* +std::string ConnectionConfig::encodedPassword() const +{ + return m_encodedPassword; +} - PGHOST behaves the same as the host connection parameter. - PGHOSTADDR behaves the same as the hostaddr connection parameter. This can be set instead of or in addition to PGHOST to avoid DNS lookup overhead. +void ConnectionConfig::setEncodedPassword(const std::string &encodedPassword) +{ + m_dirty = true; + m_encodedPassword = encodedPassword; +} + +/* + + PGHOST behaves the same as the host connection parameter. + PGHOSTADDR behaves the same as the hostaddr connection parameter. This can be set instead of or in addition to PGHOST to avoid DNS lookup overhead. PGPORT behaves the same as the port connection parameter. PGDATABASE behaves the same as the dbname connection parameter. PGUSER behaves the same as the user connection parameter. diff --git a/pglablib/ConnectionConfig.h b/pglablib/ConnectionConfig.h index 181909c..d08b6ca 100644 --- a/pglablib/ConnectionConfig.h +++ b/pglablib/ConnectionConfig.h @@ -105,9 +105,6 @@ public: const char * const * getKeywords() const; const char * const * getValues() const; - PasswordState passwordState() const; - void setPasswordState(PasswordState password_state); - bool isSameDatabase(const ConnectionConfig &rhs) const; void writeToEnvironment(QProcessEnvironment &env) const; @@ -118,6 +115,9 @@ public: bool operator==(QUuid id) const { return m_uuid == id; } QString makeLongDescription() const; + std::string encodedPassword() const; + void setEncodedPassword(const std::string &encodedPassword); + private: QUuid m_uuid; std::string m_name; @@ -136,7 +136,7 @@ private: std::string m_sslCrl; std::string m_applicationName; - PasswordState m_passwordState = PasswordState::NotStored; + std::string m_encodedPassword; bool m_dirty = false; ConnectionGroup* m_group;