Store encrypted passwords with connections.

Closes #22 as encrypted password is now deleted as part of the connection record.
This commit is contained in:
eelke 2019-09-01 14:07:58 +02:00
parent e5ae9663c4
commit d489f11e52
11 changed files with 252 additions and 695 deletions

61
core/KeyStrengthener.cpp Normal file
View file

@ -0,0 +1,61 @@
#include "KeyStrengthener.h"
#include <botan/scrypt.h>
#include <botan/base64.h>
#include <QSqlError>
#include <QSqlQuery>
#include <QVariant>
KeyStrengthener::KeyStrengthener(std::unique_ptr<Botan::PasswordHash> hasher, Botan::secure_vector<uint8_t> 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<uint8_t> KeyStrengthener::derive(const std::string &passphrase)
{
Botan::secure_vector<uint8_t> 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<Botan::Scrypt*>(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();
}
}

29
core/KeyStrengthener.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef KEYSTRENGTHENER_H
#define KEYSTRENGTHENER_H
#include <QSqlDatabase>
#include <botan/pwdhash.h>
#include <botan/secmem.h>
#include <memory>
class KeyStrengthener {
public:
KeyStrengthener() = default;
KeyStrengthener(std::unique_ptr<Botan::PasswordHash> hasher, Botan::secure_vector<uint8_t> salt, size_t keysize);
KeyStrengthener(const KeyStrengthener&) = delete;
KeyStrengthener& operator=(const KeyStrengthener &) = delete;
KeyStrengthener(KeyStrengthener &&rhs);
KeyStrengthener& operator=(KeyStrengthener &&rhs);
Botan::secure_vector<uint8_t> derive(const std::string &passphrase);
void saveParams(QSqlDatabase &db, const QString &table_name);
private:
std::unique_ptr<Botan::PasswordHash> m_hasher;
Botan::secure_vector<uint8_t> m_salt;
size_t m_keySize;
};
#endif // KEYSTRENGTHENER_H

View file

@ -7,11 +7,11 @@
#include <botan/hash.h>
#include <botan/auto_rng.h>
#include <botan/base64.h>
//#include <botan/psk_db_sql.h>
#include <botan/psk_db.h>
//#include <botan/sqlite3.h>
#include <botan/scrypt.h>
#include <botan/nist_keywrap.h>
#include <botan/base64.h>
#include <botan/mac.h>
#include <botan/block_cipher.h>
#include <boost/lexical_cast.hpp>
namespace {
@ -26,141 +26,59 @@ namespace {
};
class QPSK_Database : public Botan::Encrypted_PSK_Database
}
using namespace Botan;
class PasswordCryptoEngine {
public:
PasswordCryptoEngine(const secure_vector<uint8_t>& 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<uint8_t>& 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<std::string> kv_get_all() const override
{
QSqlQuery q(m_db);
q.prepare("SELECT psk_name FROM " + m_tableName);
std::set<std::string> 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<uint8_t> PasswordManager::KeyStrengthener::derive(const std::string &passphrase)
{
Botan::secure_vector<uint8_t> 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<Botan::Scrypt*>(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<uint8_t> wrapped_name =
nist_key_wrap_padded(cast_char_ptr_to_uint8(name.data()),
name.size(),
*m_cipher);
std::unique_ptr<BlockCipher> wrap_cipher(m_cipher->clone());
wrap_cipher->set_key(m_hmac->process(wrapped_name));
const std::vector<uint8_t> wrapped_key = nist_key_wrap_padded(val, len, *wrap_cipher);
return base64_encode(wrapped_key);
}
secure_vector<uint8_t> get(const std::string& name, const std::string &wrapped_key) const
{
const std::vector<uint8_t> wrapped_name =
nist_key_wrap_padded(cast_char_ptr_to_uint8(name.data()),
name.size(),
*m_cipher);
const secure_vector<uint8_t> val = base64_decode(wrapped_key);
std::unique_ptr<BlockCipher> 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<BlockCipher> m_cipher;
std::unique_ptr<MessageAuthenticationCode> m_hmac;
};
// -------------------------
@ -173,18 +91,27 @@ bool PasswordManager::initialized(QSqlDatabase& db)
return isPskStoreInitialized(db);
}
std::tuple<Botan::secure_vector<uint8_t>, Botan::secure_vector<uint8_t>>
PasswordManager::deriveKey(KeyStrengthener &ks, QString passphrase)
{
auto master_key = ks.derive(passphrase.toUtf8().data());
std::unique_ptr<Botan::HashFunction> 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<Botan::HashFunction> 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<QPSK_Database>(master_key, db, m_passwordTableName);
m_cryptoEngine = std::make_unique<PasswordCryptoEngine>(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<Botan::HashFunction> 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<size_t>(hash_b64.size()));
if (hash_bin == mkh_bin) {
m_pskDatabase = std::make_unique<QPSK_Database>(master_key, db, m_passwordTableName);
m_cryptoEngine = std::make_unique<PasswordCryptoEngine>(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<const uint8_t*>(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<uint8_t> decoded = m_cryptoEngine->get(id, encpwd);
return std::string(reinterpret_cast<const char*>(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<Botan::PasswordHash> pwh;
size_t key_size = 64;
Botan::secure_vector<uint8_t> salt(key_size);
Botan::AutoSeeded_RNG rng;

View file

@ -2,23 +2,19 @@
#define PASSWORDMANAGER_H
#include "Expected.h"
#include "KeyStrengthener.h"
#include <QSqlDatabase>
#include <botan/secmem.h>
#include <string>
#include <tuple>
#include <memory>
#include <botan/pwdhash.h>
//#include <botan/botan.h>
//#include <botan/symkey.h>
#include <map>
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<Botan::Encrypted_PSK_Database> m_pskDatabase;
std::unique_ptr<PasswordCryptoEngine> m_cryptoEngine;
bool isPskStoreInitialized(QSqlDatabase& db);
void initializeNewPskStore(QSqlDatabase &db);
class KeyStrengthener {
public:
KeyStrengthener() = default;
KeyStrengthener(std::unique_ptr<Botan::PasswordHash> hasher, Botan::secure_vector<uint8_t> 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<uint8_t> derive(const std::string &passphrase);
void saveParams(QSqlDatabase &db, const QString &table_name);
private:
std::unique_ptr<Botan::PasswordHash> m_hasher;
Botan::secure_vector<uint8_t> m_salt;
size_t m_keySize;
};
/// Get PasswordHash from parameters in database
KeyStrengthener getKeyStrengthener(QSqlDatabase &db);
KeyStrengthener createKeyStrengthener();
std::tuple<Botan::secure_vector<uint8_t>, Botan::secure_vector<uint8_t>>
deriveKey(KeyStrengthener &ks, QString passphrase);
};

View file

@ -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 \

View file

@ -25,10 +25,6 @@ ConnectionController::~ConnectionController()
void ConnectionController::init()
{
//std::string dbfilename = QDir::toNativeSeparators(GetUserConfigDatabaseName()).toUtf8().data();
//m_userConfigDatabase = std::make_shared<Botan::Sqlite3_Database>(dbfilename);
m_passwordManager = std::make_shared<PasswordManager>();
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<ConnectionNode*>(index.internalPointer());
return dynamic_cast<ConnectionConfig*>(node);
}
ConnectionGroup* getGroupFromModelIndex(QModelIndex index)
{
if (!index.isValid())
return nullptr;
auto node = static_cast<ConnectionNode*>(index.internalPointer());
return dynamic_cast<ConnectionGroup*>(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<PasswordManager> 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;
}

View file

@ -36,6 +36,8 @@ public:
void editConnection(QModelIndex index);
void addGroup();
void removeGroup(QModelIndex index);
std::shared_ptr<PasswordManager> 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();

View file

@ -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<bool, QSqlError> 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<int>(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<int>(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<int>(cc.passwordState()));
// }
// template <typename S, typename T>
// bool in_range(T value)
// {
// return value >= std::numeric_limits<S>::min() && value <= std::numeric_limits<S>::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<uint16_t>(p)) {
// p = 0; // let the user re-enter a valid value
// }
// cc.setPort(static_cast<uint16_t>(p));
// cc.setUser(qvarToStdStr(settings.value("user")));
// //cc.setPassword(qvarToStdStr(settings.value("password")));
// cc.setDbname(qvarToStdStr(settings.value("dbname")));
// cc.setSslMode(static_cast<SslMode>(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<PasswordState>(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<ConnectionConfig> ConnectionListModel::get(int row)
{
if (row < m_connections.size()) {
return m_connections.at(row);
}
return Expected<ConnectionConfig>::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<PasswordState>(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<ConnectionGroup>();
// g1->name = "Testing";
// for (int i = 1; i < 3; ++i) {
// auto cc = std::make_shared<ConnectionConfig>();
// cc->setUuid(QUuid::createUuid());
// cc->setName("testconn " + std::to_string(i));
// g1->add(cc);
// }
// auto g2 = std::make_shared<ConnectionGroup>();
// g2->name = "Production";
// for (int i = 1; i < 4; ++i) {
// auto cc = std::make_shared<ConnectionConfig>();
// 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<ConnectionNode*>(node));
// void *p = nullptr;
// if (parent.isValid()) {
// auto cg = static_cast<ConnectionGroup *>(parent.internalPointer());
// auto cc = &cg->connections().at(row);
// p = const_cast<ConnectionConfig*>(cc);
// }
// else {
// p = const_cast<ConnectionGroup*>(&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<ConnectionNode*>(parent.internalPointer());
if (auto group = dynamic_cast<ConnectionGroup*>(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<ConnectionNode*>(index.internalPointer());
return dynamic_cast<ConnectionConfig*>(node);
}
ConnectionGroup *ConnectionTreeModel::getGroupFromModelIndex(QModelIndex index)
{
if (!index.isValid())
return nullptr;
auto node = static_cast<ConnectionNode*>(index.internalPointer());
return dynamic_cast<ConnectionGroup*>(node);
}
std::optional<QSqlError> ConnectionTreeModel::saveToDb(const ConnectionConfig &cc)
{
return SaveConnectionConfig(m_db, cc, cc.parent()->conngroup_id);

View file

@ -64,6 +64,10 @@ public:
std::variant<int, QSqlError> addGroup(QString group_name);
std::optional<QSqlError> removeGroup(int row);
int findGroup(int conngroup_id) const;
static ConnectionConfig* getConfigFromModelIndex(QModelIndex index);
static ConnectionGroup* getGroupFromModelIndex(QModelIndex index);
private:
using Groups = QVector<std::shared_ptr<ConnectionGroup>>;
@ -78,61 +82,4 @@ private:
std::optional<QSqlError> 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<ConnectionConfig> 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<ConnectionConfig>;
ConnectionList m_connections;
QString iniFileName();
};
#endif
#endif // CONNECTIONLISTMODEL_H

View file

@ -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;
}
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.
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.

View file

@ -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;