Passwords are now saved in a password manager.

The password manager uses strong encryption using a key derived from the passphrase using
scrypt key strengthening algorithm. This ensures encryption is performed using a strong key
and that brute forcing the passphrase is time consuming.

If the user loses his passphrase no recovery is possible.
This commit is contained in:
eelke 2018-11-08 21:50:49 +01:00
parent 2230a4bd61
commit e36924c087
27 changed files with 605 additions and 346 deletions

View file

@ -1,22 +1,128 @@
#include "PasswordManager.h"
//#include <botan/filters.h>
//#include <botan/pipe.h>
//#include <botan/sha2_64.h>
//#include <botan/hash.h>
//#include <botan/hmac.h>
//#include <botan/pbkdf2.h>
//#include <botan/rng.h>
//#include <botan/base64.h>
//#include <botan/loadstor.h>
//#include <botan/mem_ops.h>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
#include <QVariant>
#include <botan/hash.h>
#include <botan/auto_rng.h>
#include <botan/base64.h>
#include <botan/psk_db_sql.h>
#include <botan/sqlite3.h>
//#include <botan/psk_db_sql.h>
#include <botan/psk_db.h>
//#include <botan/sqlite3.h>
#include <botan/scrypt.h>
#include <boost/lexical_cast.hpp>
namespace {
class SqlException : public std::runtime_error {
public:
QSqlError error;
SqlException(const QSqlError &err)
: std::runtime_error(err.text().toUtf8().data())
, error(err)
{}
};
class QPSK_Database : public Botan::Encrypted_PSK_Database
{
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)
@ -27,69 +133,129 @@ Botan::secure_vector<uint8_t> PasswordManager::KeyStrengthener::derive(const std
return master_key;
}
void PasswordManager::KeyStrengthener::saveParams(std::shared_ptr<Botan::Sqlite3_Database> db, const std::string &table_name)
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
auto stmt = db->new_statement("INSERT INTO " + table_name + "(id, algo, i1, i2, i3, ks, salt) VALUES(?1, ?2, ?3, ?4, ?5)");
stmt->bind(1, 1);
stmt->bind(2, "Scrypt");
stmt->bind(3, i1);
stmt->bind(4, i2);
stmt->bind(5, i3);
stmt->bind(6, m_keySize);
stmt->bind(7, Botan::base64_encode(m_salt));
stmt->spin();
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);
}
}
// -------------------------
void PasswordManager::openDatabase(std::shared_ptr<Botan::Sqlite3_Database> db, std::string passphrase)
PasswordManager::PasswordManager() = default;
PasswordManager::~PasswordManager() = default;
bool PasswordManager::initialized(QSqlDatabase& db)
{
// std::string psk_db_file_name;
// auto db = std::make_shared<Botan::Sqlite3_Database>(psk_db_file_name);
KeyStrengthener ks;
// if (database exists)
if (isPskStoreInitialized(db)) {
ks = getKeyStrengthener(db);
}
else {
initializeNewPskStore(db);
ks = createKeyStrengthener();
ks.saveParams(db, m_secretAlgoTableName);
}
Botan::secure_vector<uint8_t> master_key = ks.derive(passphrase);
m_pskDatabase = std::make_unique<Botan::Encrypted_PSK_Database_SQL>(master_key, db, m_passwordTableName);
return isPskStoreInitialized(db);
}
bool PasswordManager::createDatabase(QSqlDatabase &db, QString passphrase)
{
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());
QSqlQuery q_ins_hash(db);
q_ins_hash.prepare("INSERT INTO " + m_secretHashTableName + "(id, hash) VALUES(:id, :hash)");
q_ins_hash.bindValue(":id", 1);
q_ins_hash.bindValue(":hash", mkh);
if (!q_ins_hash.exec()) {
auto err = q_ins_hash.lastError();
qDebug() << err.text();
throw SqlException(err);
}
m_pskDatabase = std::make_unique<QPSK_Database>(master_key, db, m_passwordTableName);
return true;
}
return false;
}
bool PasswordManager::openDatabase(QSqlDatabase &db, QString passphrase)
{
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();
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);
return true;
}
}
}
return false;
}
void PasswordManager::closeDatabase()
{
m_pskDatabase.reset();
}
bool PasswordManager::locked() const
{
return m_pskDatabase == nullptr;
}
void PasswordManager::set(const std::string &id, const std::string &passwd)
{
if (m_pskDatabase) {
m_pskDatabase->set_str(id, passwd);
}
else {
throw PasswordManagerLockedException();
}
}
std::string PasswordManager::get(const std::string &id, const std::string &passwd)
bool PasswordManager::get(const std::string &id, std::string &password)
{
if (m_pskDatabase) {
try {
password = m_pskDatabase->get_str(id);
return true;
}
catch (const Botan::Invalid_Argument &) {
// not present
return false;
}
}
else {
throw PasswordManagerLockedException();
@ -99,7 +265,7 @@ std::string PasswordManager::get(const std::string &id, const std::string &passw
void PasswordManager::remove(const std::string &id)
{
if (m_pskDatabase) {
m_pskDatabase->remove(id);
}
else {
throw PasswordManagerLockedException();
@ -107,57 +273,100 @@ void PasswordManager::remove(const std::string &id)
}
void PasswordManager::initializeNewPskStore(std::shared_ptr<Botan::Sqlite3_Database> db)
void PasswordManager::initializeNewPskStore(QSqlDatabase &db)
{
// Create tables
// - psk_masterkey_algo
// - psk_passwd
std::string create_statement =
// // Create tables
// // - psk_masterkey_algo
// // - psk_passwd
{
QSqlQuery create_tbl(db);
create_tbl.prepare(
"CREATE TABLE IF NOT EXISTS " + m_secretAlgoTableName + "( \n"
" id INTEGER PRIMARY KEY \n"
" algo TEXT \n"
" i1 INTEGER \n"
" i2 INTEGER \n"
" i3 INTEGER \n"
" ks INTEGER \n"
" id INTEGER PRIMARY KEY, \n"
" algo TEXT, \n"
" i1 INTEGER, \n"
" i2 INTEGER, \n"
" i3 INTEGER, \n"
" ks INTEGER, \n"
" salt TEXT \n"
");";
db->create_table(create_statement);
");");
if (!create_tbl.exec()) {
// auto sql_error = create_tbl.lastError();
// throw std::runtime_error("create table failed");
auto err = create_tbl.lastError();
throw SqlException(err);
}
}
// db->create_table(
QSqlQuery create_tbl(db);
create_tbl.prepare(
"CREATE TABLE IF NOT EXISTS " + m_secretHashTableName + "( \n"
" id INTEGER PRIMARY KEY, \n"
" hash TEXT \n"
");");
if (!create_tbl.exec()) {
auto err = create_tbl.lastError();
throw SqlException(err);
}
}
bool PasswordManager::isPskStoreInitialized(std::shared_ptr<Botan::Sqlite3_Database> db)
bool PasswordManager::isPskStoreInitialized(QSqlDatabase& db)
{
// Is the table with the secret data present and filled?
auto stmt = db->new_statement("SELECT name FROM sqlite_master WHERE type='table' AND name=?1");
stmt->bind(1, m_secretAlgoTableName);
bool ok = stmt->step();
if (ok) {
auto stmt = db->new_statement("SELECT algo FROM " + m_secretAlgoTableName + " WHERE id=1");
return stmt->step();
QSqlQuery query(db);
query.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=:name");
query.bindValue(":name", m_secretAlgoTableName);
if (!query.exec()) {
auto err = query.lastError();
throw SqlException(err);
}
if (!query.next()) {
return false;
}
query.bindValue(":name", m_secretHashTableName);
if (!query.exec()) {
auto err = query.lastError();
throw SqlException(err);
}
if (!query.next()) {
return false;
}
QSqlQuery sel_algo("SELECT algo FROM " + m_secretAlgoTableName + " WHERE id=1", db);
if (!sel_algo.next()) {
return false;
}
QSqlQuery sel_hash("SELECT hash FROM " + m_secretHashTableName + " WHERE id=1", db);
if (!sel_hash.next()) {
return false;
}
return true;
}
PasswordManager::KeyStrengthener PasswordManager::getKeyStrengthener(std::shared_ptr<Botan::Sqlite3_Database> db)
PasswordManager::KeyStrengthener PasswordManager::getKeyStrengthener(QSqlDatabase &db)
{
auto stmt = db->new_statement("SELECT algo, i1, i2, i3, ks, salt FROM " + m_secretAlgoTableName + " WHERE id=1");
if (stmt->step()) {
std::string algo = stmt->get_str(0);
size_t i1 = boost::lexical_cast<size_t>(stmt->get_str(1));
size_t i2 = boost::lexical_cast<size_t>(stmt->get_str(2));
size_t i3 = boost::lexical_cast<size_t>(stmt->get_str(3));
size_t ks = boost::lexical_cast<size_t>(stmt->get_str(4));
QSqlQuery query("SELECT algo, i1, i2, i3, ks, salt FROM " + m_secretAlgoTableName + " WHERE id=1", db);
if (query.next()) {
std::string algo = query.value(0).toString().toUtf8().data();
size_t i1 = query.value(1).toUInt();
size_t i2 = query.value(2).toUInt();
size_t i3 = query.value(3).toUInt();
size_t ks = query.value(4).toUInt();
auto salt = query.value(5).toString().toUtf8();
auto pwh_fam = Botan::PasswordHashFamily::create(algo);
return KeyStrengthener(
pwh_fam->from_params(i1, i2, i3),
Botan::base64_decode(stmt->get_str(5)),
Botan::base64_decode(salt.data(), static_cast<size_t>(salt.size())),
ks
);
}
else {
throw std::runtime_error("fail");
}
}

View file

@ -2,6 +2,7 @@
#define PASSWORDMANAGER_H
#include "Expected.h"
#include <QSqlDatabase>
#include <botan/secmem.h>
#include <string>
#include <memory>
@ -16,8 +17,8 @@
namespace Botan {
class Encrypted_PSK_Database_SQL;
class Sqlite3_Database;
class Encrypted_PSK_Database;
//class Sqlite3_Database;
class PasswordHash;
}
@ -41,21 +42,31 @@ public:
Error
};
PasswordManager() = default;
PasswordManager();
~PasswordManager();
void openDatabase(std::shared_ptr<Botan::Sqlite3_Database> db, std::string passphrase);
/** Check if it has been initialized before.
*
* If returns false then use createDatabase to set it up
* else use openDatabase to get access.
*/
bool initialized(QSqlDatabase &db);
bool createDatabase(QSqlDatabase &db, QString passphrase);
bool openDatabase(QSqlDatabase &db, QString passphrase);
void closeDatabase();
bool locked() const;
void set(const std::string &id, const std::string &passwd);
std::string get(const std::string &id, const std::string &passwd);
bool get(const std::string &id, std::string &password);
void remove(const std::string &id);
private:
std::string m_passwordTableName = "psk_passwd";
std::string m_secretAlgoTableName = "psk_masterkey_algo";
std::unique_ptr<Botan::Encrypted_PSK_Database_SQL> m_pskDatabase;
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;
bool isPskStoreInitialized(std::shared_ptr<Botan::Sqlite3_Database> db);
void initializeNewPskStore(std::shared_ptr<Botan::Sqlite3_Database> db);
bool isPskStoreInitialized(QSqlDatabase& db);
void initializeNewPskStore(QSqlDatabase &db);
class KeyStrengthener {
public:
@ -74,6 +85,7 @@ private:
, m_salt (std::move(rhs.m_salt))
, m_keySize(rhs.m_keySize)
{}
KeyStrengthener& operator=(KeyStrengthener &&rhs)
{
if (&rhs != this) {
@ -85,7 +97,7 @@ private:
}
Botan::secure_vector<uint8_t> derive(const std::string &passphrase);
void saveParams(std::shared_ptr<Botan::Sqlite3_Database> db, const std::string &table_name);
void saveParams(QSqlDatabase &db, const QString &table_name);
private:
std::unique_ptr<Botan::PasswordHash> m_hasher;
Botan::secure_vector<uint8_t> m_salt;
@ -93,7 +105,7 @@ private:
};
/// Get PasswordHash from parameters in database
KeyStrengthener getKeyStrengthener(std::shared_ptr<Botan::Sqlite3_Database> db);
KeyStrengthener getKeyStrengthener(QSqlDatabase &db);
KeyStrengthener createKeyStrengthener();
};

View file

@ -5,6 +5,7 @@
#-------------------------------------------------
QT -= gui
QT += sql
TARGET = core
TEMPLATE = lib

View file

@ -27,6 +27,7 @@ namespace {
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>
@ -56,6 +57,12 @@ namespace {
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);
}

View file

@ -19,6 +19,11 @@ public:
return m_connections.at(idx);
}
void setConfigByIdx(size_t idx, const ConnectionConfig &cc)
{
m_connections[idx] = cc;
}
size_t createNew();
void remove(size_t idx, size_t count);

View file

@ -140,9 +140,9 @@ void ConnectionListModel::newItem()
emit dataChanged(idx, idx);
}
Expected<ConnectionConfig> ConnectionListModel::get(int row)
Expected<ConnectionConfig> ConnectionListModel::get(size_t row)
{
if (row >= 0 && row < (int)m_connections->size()) {
if (row < m_connections->size()) {
return m_connections->getConfigByIdx(row);
}
else {
@ -204,5 +204,6 @@ void ConnectionListModel::save(size_t index)
void ConnectionListModel::save(size_t index, const ConnectionConfig &cc)
{
m_connections->setConfigByIdx(index, cc);
m_connections->save(index);
}

View file

@ -20,7 +20,7 @@ class ConnectionListModel : public QAbstractListModel {
public:
ConnectionListModel(ConnectionList *conns, QObject *parent);
ConnectionListModel(const ConnectionListModel&) = delete;
~ConnectionListModel();
~ConnectionListModel() override;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
virtual int columnCount(const QModelIndex &/*parent*/) const override;
@ -30,7 +30,7 @@ public:
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
void newItem();
Expected<ConnectionConfig> get(int row);
Expected<ConnectionConfig> get(size_t row);
virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
void save();

View file

@ -2,7 +2,7 @@
#define CONNECTIONMANAGERWINDOW_H
#include <QMainWindow>
#include <boost/optional.hpp>
#include <optional>
namespace Ui {
class ConnectionManagerWindow;
@ -19,7 +19,7 @@ class QStandardItemModel;
class ConnectionManagerWindow : public QMainWindow {
Q_OBJECT
public:
explicit ConnectionManagerWindow(MasterController *master, QWidget *parent = 0);
explicit ConnectionManagerWindow(MasterController *master, QWidget *parent = nullptr);
~ConnectionManagerWindow();
private slots:
@ -36,7 +36,7 @@ private:
QDataWidgetMapper *m_mapper = nullptr;
MasterController *m_masterController;
boost::optional<size_t> prevSelection;
std::optional<size_t> prevSelection;
void setupWidgetMappings();
};

View file

@ -98,7 +98,7 @@ CrudModel::Value CrudModel::getData(const QModelIndex &index) const
//Oid o = m_roData->type(col);
// First see if we have buffered editted values that still need saving
boost::optional<Value> val;
std::optional<Value> val;
if (row_mapping.pending) {
val = m_pendingRowList.getValue(col, row_mapping.rowKey);
}

View file

@ -12,7 +12,7 @@
#include <memory>
#include <tuple>
#include <vector>
#include <boost/optional.hpp>
#include <optional>
class PgConstraint;
class OpenDatabase;
@ -121,7 +121,7 @@ private:
// std::shared_ptr<RowList> resultToRowList(std::shared_ptr<Pgsql::Result> result);
using Value = boost::optional<std::string>;
using Value = std::optional<std::string>;
/** Used to remember the changes that have been made to rows.
*
@ -194,7 +194,7 @@ private:
iter->second.setValue(col, value);
}
boost::optional<Value> getValue(int col, int row) const
std::optional<Value> getValue(int col, int row) const
{
auto iter = m_rows.find(row);
if (iter != m_rows.end()) {
@ -204,7 +204,7 @@ private:
return cell->second;
}
return boost::none;
return std::nullopt;
}
auto begin() { return m_rows.begin(); }
@ -233,7 +233,7 @@ private:
ASyncWindow * m_asyncWindow;
std::shared_ptr<OpenDatabase> m_database;
PgClass m_table;
boost::optional<PgConstraint> m_primaryKey;
std::optional<PgConstraint> m_primaryKey;
ASyncDBConnection m_dbConn;
bool callLoadData = false;

View file

@ -2,11 +2,30 @@
#include "ConnectionManagerWindow.h"
#include "ConnectionList.h"
#include "ConnectionListModel.h"
#include "PasswordManager.h"
#include "MainWindow.h"
#include "ServerWindow.h"
#include "BackupDialog.h"
#include "PasswordPromptDialog.h"
#include <QDebug>
#include <QDir>
#include <QStandardPaths>
#include <botan/sqlite3.h>
namespace {
QString GetUserConfigDatabaseName()
{
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(path);
if (!dir.exists()) {
dir.mkpath(".");
}
path += "/pglabuser.db";
return path;
}
}
MasterController::MasterController(QObject *parent) : QObject(parent)
{}
@ -20,6 +39,21 @@ MasterController::~MasterController()
void MasterController::init()
{
//std::string dbfilename = QDir::toNativeSeparators(GetUserConfigDatabaseName()).toUtf8().data();
//m_userConfigDatabase = std::make_shared<Botan::Sqlite3_Database>(dbfilename);
m_userConfigDatabase = QSqlDatabase::addDatabase("QSQLITE");
m_userConfigDatabase.setDatabaseName(GetUserConfigDatabaseName());
if (!m_userConfigDatabase.open()) {
qDebug() << "Error: connection with database fail";
}
else {
qDebug() << "Database: connection ok";
}
m_passwordManager = std::make_shared<PasswordManager>();
m_connectionList = new ConnectionList;
m_connectionList->load();
m_connectionListModel = new ConnectionListModel(m_connectionList, this);
@ -41,8 +75,8 @@ void MasterController::openSqlWindowForConnection(size_t connection_index)
if (res.valid()) {
auto cc = res.get();
m_connectionListModel->save(connection_index, cc);
if (retrieveConnectionPassword(cc)) {
m_connectionListModel->save(connection_index, cc);
// TODO instead of directly openening the mainwindow
// do async connect and only open window when we have
// working connection
@ -55,55 +89,65 @@ void MasterController::openSqlWindowForConnection(size_t connection_index)
}
void MasterController::openBackupDlgForConnection(int connection_index)
void MasterController::openBackupDlgForConnection(size_t connection_index)
{
auto res = m_connectionListModel->get(connection_index);
if (res.valid()) {
auto cc = res.get();
retrieveConnectionPassword(cc);
if (retrieveConnectionPassword(cc)) {
m_connectionListModel->save(connection_index, cc);
auto w = new BackupDialog(nullptr); //new ServerWindow(this, nullptr);
w->setAttribute( Qt::WA_DeleteOnClose );
w->setConfig(cc);
w->show();
}
}
}
void MasterController::openServerWindowForConnection(int connection_index)
void MasterController::openServerWindowForConnection(size_t connection_index)
{
auto res = m_connectionListModel->get(connection_index);
if (res.valid()) {
auto cc = res.get();
retrieveConnectionPassword(cc);
if (retrieveConnectionPassword(cc)) {
m_connectionListModel->save(connection_index, cc);
auto w = new ServerWindow(this, nullptr);
w->setAttribute( Qt::WA_DeleteOnClose );
w->setConfig(cc);
w->show();
}
}
}
bool MasterController::retrieveConnectionPassword(ConnectionConfig &cc)
{
// Look at config
// - is password required, how do we know?
// - IF is password stored in pskdb
// - ask pskdb for password
// - ELSE
// - ask user for password
auto pw_state = cc.passwordState();
if (pw_state == PasswordState::NotNeeded) {
return true;
}
else if (pw_state == PasswordState::SavedPasswordManager) {
std::string pw;
bool result = getPasswordFromPskdb(getPskId(cc), pw);
if (result) {
cc.setPassword(pw);
return true;
}
}
// Geen else hier want als voorgaande blok niet geretourneerd heeft moeten we wachtwoord
// ook aan de gebruiker vragen zoals hier gebeurd.
QString str = ConnectionListModel::makeLongDescription(cc);
auto dlg = std::make_unique<PasswordPromptDialog>(nullptr);
dlg->setConnectionDescription(str);
auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::SaveOption, nullptr);
dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str));
int exec_result = dlg->exec();
if (exec_result == QDialog::Accepted) {
cc.setPassword(dlg->password().toUtf8().data());
// - IF user checked remember password
// - ask pskdb to store password
std::string password = dlg->password().toUtf8().data();
cc.setPassword(password);
if (dlg->saveChecked()) {
storePasswordInPskdb(getPskId(cc), password);
cc.setPasswordState(PasswordState::SavedPasswordManager);
}
return true;
}
return false;
@ -112,25 +156,71 @@ bool MasterController::retrieveConnectionPassword(ConnectionConfig &cc)
bool MasterController::getPasswordFromPskdb(const std::string &password_id, std::string &password)
{
// func: getPasswordFromPskdb
// IF pskdb locked
// prompt user for pskdb passphrase
// unlock pskdb
// get pwd
if (!UnlockPasswordManagerIfNeeded())
return false;
return m_passwordManager->get(password_id, password);
}
bool MasterController::storePasswordInPskdb(const std::string &password_id, const std::string password)
{
// func: storePasswordInPskdb
// IF pskdb not setup
// notify user and ask for passphrase
// init pskdb
// ELSE
// IF pskdb locked
// ask for passphrase
// unlock
// store pwd
if (!UnlockPasswordManagerIfNeeded())
return false;
m_passwordManager->set(password_id, password);
return true;
}
bool MasterController::UnlockPasswordManagerIfNeeded()
{
if (m_passwordManager->initialized(m_userConfigDatabase)) {
if (!m_passwordManager->locked())
return true;
while (true) {
// ask user for passphrase
auto dlg = std::make_unique<PasswordPromptDialog>(nullptr, nullptr);
dlg->setDescription(tr("Enter passphrase for password manager"));
int exec_result = dlg->exec();
bool ok = (exec_result == QDialog::Accepted);
// IF user gave OK
if (ok) {
if (m_passwordManager->openDatabase(m_userConfigDatabase, dlg->password())) {
return true;
}
}
else {
return false;
}
}
}
else {
// Ask user for passphrase + confirmation, clearly instruct this is first setup
// create
auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::ConfirmPassword, nullptr);
dlg->setDescription(tr("Enter passphrase for password manager initialization"));
int exec_result = dlg->exec();
if (exec_result == QDialog::Accepted) {
QString passphrase = dlg->password();
if (m_passwordManager->createDatabase(m_userConfigDatabase, passphrase)) {
return true;
}
}
}
return false;
}
std::string MasterController::getPskId(const ConnectionConfig &cc)
{
std::string id = "dbpw/";
id += cc.uuid().toString().toUtf8().data();
return id;
}
//std::shared_ptr<Botan::Sqlite3_Database> MasterController::getUserConfigDatabase()
//{
// return m_userConfigDatabase;
//}

View file

@ -2,14 +2,21 @@
#define MASTERCONTROLLER_H
#include <QObject>
#include <QSqlDatabase>
#include <atomic>
#include <future>
#include <map>
#include <memory>
//namespace Botan {
// class Sqlite3_Database;
//}
class ConnectionConfig;
class ConnectionList;
class ConnectionListModel;
class ConnectionManagerWindow;
class PasswordManager;
/** \brief Controller class responsible for all things global.
*/
@ -30,8 +37,10 @@ public:
void showConnectionManager();
void openSqlWindowForConnection(size_t connection_index);
void openServerWindowForConnection(int connection_index);
void openBackupDlgForConnection(int connection_index);
void openServerWindowForConnection(size_t connection_index);
void openBackupDlgForConnection(size_t connection_index);
// std::shared_ptr<Botan::Sqlite3_Database> getUserConfigDatabase();
signals:
@ -41,6 +50,12 @@ private:
ConnectionList *m_connectionList = nullptr;
ConnectionListModel *m_connectionListModel = nullptr;
ConnectionManagerWindow *m_connectionManagerWindow = nullptr;
//std::shared_ptr<Botan::Sqlite3_Database> m_userConfigDatabase;
QSqlDatabase m_userConfigDatabase;
/** Using long lived object so it can remember its master password for sometime
* if the user wishes it.
*/
std::shared_ptr<PasswordManager> m_passwordManager;
/** Retrieves the connection password from the user (directly or through the psk db)
*
@ -50,6 +65,10 @@ private:
bool getPasswordFromPskdb(const std::string &password_id, std::string &password);
bool storePasswordInPskdb(const std::string &password_id, const std::string password);
bool UnlockPasswordManagerIfNeeded();
static std::string getPskId(const ConnectionConfig &cc);
};
#endif // MASTERCONTROLLER_H

View file

@ -1,45 +0,0 @@
#include "PassPhraseForm.h"
#include "ui_PassPhraseForm.h"
PassPhraseForm::PassPhraseForm(QWidget *parent) :
QWidget(parent),
ui(new Ui::PassPhraseForm)
{
ui->setupUi(this);
}
PassPhraseForm::~PassPhraseForm()
{
delete ui;
}
/*
Password strength calculation:
seperate characters in couple of groups
For the use of characters from each group a certain value is added to the base value
which is meant to signify the size of the set of characters the password is based on.
Some analysis of relative positions might be required! Predictable placement of special charachters and uppercase/lowercase or numbers
should be penalized.
These calculations should result in a search space size per character
the base to the power of the length of the password gives the resulting strength
from this result we take the 10 log to get the magnitude of the value.
a-z 1:3 2:7 3:13 4:26
A-Z 1:3 2:7 3:13 4:26
0-9 1:4 2:10
`~!@#$%^&*()_-=+[{]};:'",<.>/?\| 1:4 2:8 3:16 4:32
space +1
Straf punten
alleen speciaal karakter aan eind van string -8
alleen hoofdletter aan begin van wachtwoord -6
la-~ZDv4E-O*y]C
bYGWlDyeKKbcZBjoWX
*/

View file

@ -1,22 +0,0 @@
#ifndef PASSPHRASEFORM_H
#define PASSPHRASEFORM_H
#include <QWidget>
namespace Ui {
class PassPhraseForm;
}
class PassPhraseForm : public QWidget
{
Q_OBJECT
public:
explicit PassPhraseForm(QWidget *parent = nullptr);
~PassPhraseForm();
private:
Ui::PassPhraseForm *ui;
};
#endif // PASSPHRASEFORM_H

View file

@ -1,75 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PassPhraseForm</class>
<widget class="QWidget" name="PassPhraseForm">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>397</width>
<height>228</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<property name="horizontalSpacing">
<number>20</number>
</property>
<property name="verticalSpacing">
<number>20</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Enter passphrase:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit">
<property name="maxLength">
<number>32767</number>
</property>
<property name="echoMode">
<enum>QLineEdit::NoEcho</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Repeat passphrase:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_2">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>67</number>
</property>
<property name="format">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -5,66 +5,96 @@
#include <QLabel>
#include <QLayout>
#include <QLineEdit>
#include <QPushButton>
PasswordPromptDialog::PasswordPromptDialog(QWidget *parent)
Q_DECLARE_OPERATORS_FOR_FLAGS(PasswordPromptDialog::Flags)
PasswordPromptDialog::PasswordPromptDialog(Flags flags, QWidget *parent)
: QDialog(parent)
, m_Flags(flags)
{
m_connectionLabel = new QLabel(this);
auto dialog_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
m_passwordLabel = new QLabel(this);
m_passwordInput = new QLineEdit(this);
m_passwordInput->setEchoMode(QLineEdit::Password);
m_saveCheck = new QCheckBox(this);
m_DialogButtons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
const size_t inputFieldCount = flags.testFlag(ConfirmPassword) ? 2 : 1;
auto mainLayout = new QGridLayout;
int row = 0;
mainLayout->addWidget(m_connectionLabel, row, 0, 1, 2);
++row;
mainLayout->addWidget(m_passwordLabel, row, 0);
mainLayout->addWidget(m_passwordInput, row, 1);
for (size_t idx = 0; idx < inputFieldCount; ++idx) {
auto lbl = new QLabel(this);
auto input = new QLineEdit(this);
input->setEchoMode(QLineEdit::Password);
mainLayout->addWidget(lbl, row, 0);
mainLayout->addWidget(input, row, 1);
m_passwordLabel[idx] = lbl;
m_passwordInput[idx] = input;
++row;
}
if (m_Flags.testFlag(SaveOption)) {
m_saveCheck = new QCheckBox(this);
mainLayout->addWidget(m_saveCheck, row, 1);
++row;
mainLayout->addWidget(dialog_buttons, row, 0, 1 ,2);
}
mainLayout->addWidget(m_DialogButtons, row, 0, 1 ,2);
setLayout(mainLayout);
m_passwordInput->setFocus();
m_passwordInput[0]->setFocus();
retranslateUi();
// QMetaObject::connectSlotsByName(BackupDialog);
connect(dialog_buttons, &QDialogButtonBox::accepted, this, &PasswordPromptDialog::accept);
connect(dialog_buttons, &QDialogButtonBox::rejected, this, &PasswordPromptDialog::reject);
connect(m_DialogButtons, &QDialogButtonBox::accepted, this, &PasswordPromptDialog::accept);
connect(m_DialogButtons, &QDialogButtonBox::rejected, this, &PasswordPromptDialog::reject);
connect(m_passwordInput[0], &QLineEdit::textChanged, this, &PasswordPromptDialog::passwordChanged);
if (m_passwordInput[1])
connect(m_passwordInput[1], &QLineEdit::textChanged, this, &PasswordPromptDialog::passwordChanged);
}
void PasswordPromptDialog::retranslateUi()
{
const char * context = "PasswordPromptDialog";
setWindowTitle(QApplication::translate(context, "Connection password", nullptr));
m_passwordLabel->setText(QApplication::translate(context, "Password", nullptr));
m_passwordInput->setPlaceholderText(QApplication::translate(context, "Enter password", nullptr));
m_passwordLabel[0]->setText(QApplication::translate(context, "Password", nullptr));
m_passwordInput[0]->setPlaceholderText(QApplication::translate(context, "Enter password", nullptr));
if (m_passwordLabel[1])
m_passwordLabel[1]->setText(QApplication::translate(context, "Confirm password", nullptr));
if (m_passwordInput[1])
m_passwordInput[1]->setPlaceholderText(QApplication::translate(context, "Reenter same password for confirmation", nullptr));
if (m_saveCheck)
m_saveCheck->setText(QApplication::translate(context, "Save password", nullptr));
}
void PasswordPromptDialog::setConnectionDescription(const QString &description)
void PasswordPromptDialog::updateOkEnabled()
{
m_connectionLabel->setText(QString(tr("Please provide password for connection %1")).arg(description));
bool enabled = true;
if (m_passwordInput[1])
enabled = m_passwordInput[0]->text() == m_passwordInput[1]->text();
auto btn = m_DialogButtons->button(QDialogButtonBox::Ok);
btn->setEnabled(enabled);
}
void PasswordPromptDialog::passwordChanged(const QString &)
{
updateOkEnabled();
}
void PasswordPromptDialog::setDescription(const QString &description)
{
m_connectionLabel->setText(QString("%1").arg(description));
}
QString PasswordPromptDialog::password() const
{
return m_passwordInput->text();
return m_passwordInput[0]->text();
}
//void PasswordPromptDialog::accept()
//{
bool PasswordPromptDialog::saveChecked() const
{
if (m_saveCheck)
return m_saveCheck->checkState() == Qt::Checked;
//}
//void PasswordPromptDialog::reject()
//{
//}
return false;
}

View file

@ -4,6 +4,7 @@
#include <QDialog>
class QCheckBox;
class QDialogButtonBox;
class QLabel;
class QLineEdit;
@ -11,22 +12,33 @@ class PasswordPromptDialog : public QDialog
{
Q_OBJECT
public:
explicit PasswordPromptDialog(QWidget *parent = nullptr);
enum Flag {
ConfirmPassword = 1,
SaveOption = 2
};
void setConnectionDescription(const QString &description);
Q_DECLARE_FLAGS(Flags, Flag)
//Q_FLAG(Flags)
explicit PasswordPromptDialog(Flags flags, QWidget *parent = nullptr);
void setDescription(const QString &description);
QString password() const;
bool saveChecked() const;
private:
Flags m_Flags;
QLabel *m_connectionLabel = nullptr;
QLabel *m_passwordLabel = nullptr;
QLineEdit *m_passwordInput = nullptr;
QLabel *m_passwordLabel[2] = { nullptr, nullptr };
QLineEdit *m_passwordInput[2] = { nullptr, nullptr };
QCheckBox *m_saveCheck = nullptr;
QDialogButtonBox *m_DialogButtons = nullptr;
void retranslateUi();
void updateOkEnabled();
private slots:
// void accept();
// void reject();
void passwordChanged(const QString &text);
};
#endif // PASSWORDPROMPTDIALOG_H

View file

@ -84,7 +84,6 @@ PropertyProxyModel.cpp \
SqlCodePreview.cpp \
CustomFilterSortModel.cpp \
PropertiesPage.cpp \
PassPhraseForm.cpp \
PasswordPromptDialog.cpp
HEADERS += \
@ -140,7 +139,6 @@ CustomDataRole.h \
SqlCodePreview.h \
CustomFilterSortModel.h \
PropertiesPage.h \
PassPhraseForm.h \
PasswordPromptDialog.h
FORMS += mainwindow.ui \
@ -155,8 +153,7 @@ FORMS += mainwindow.ui \
NamespaceFilterWidget.ui \
ApplicationWindow.ui \
CrudTab.ui \
CodeGenerator.ui \
PassPhraseForm.ui
CodeGenerator.ui
RESOURCES += \
resources.qrc

View file

@ -258,6 +258,19 @@ 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

View file

@ -15,10 +15,10 @@ enum class SslMode {
verify_full=5
};
enum class PasswordMode {
Unsave,
Encrypted,
DontSave
enum class PasswordState {
NotNeeded, ///< the Connection doesn't require a password
NotStored, ///< password needed but we do not know it
SavedPasswordManager, ///< Saved in the password manager
};
class QProcessEnvironment;
@ -70,6 +70,9 @@ 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;
@ -84,7 +87,7 @@ private:
std::string m_port = "5432";
std::string m_user;
std::string m_password;
std::string m_password; ///< TODO do we want to keep this here or should we remember it seperatly?
std::string m_dbname;
std::string m_sslMode;
@ -94,9 +97,11 @@ private:
std::string m_sslCrl;
std::string m_applicationName;
PasswordState m_passwordState = PasswordState::NotStored;
bool m_dirty = false;
static void strToEnv(QProcessEnvironment &env, const QString &var, const std::string &val);
static std::vector<const char*> s_keywords;

View file

@ -74,9 +74,9 @@ std::vector<PgConstraint> PgConstraintContainer::getConstraintsForRelation(Oid r
return result;
}
boost::optional<PgConstraint> PgConstraintContainer::getPrimaryForRelation(Oid relid) const
std::optional<PgConstraint> PgConstraintContainer::getPrimaryForRelation(Oid relid) const
{
boost::optional<PgConstraint> result;
std::optional<PgConstraint> result;
for (const auto &e : m_container) {
if (e.relid == relid && e.type == ConstraintType::PrimaryKey) {
result = e;

View file

@ -5,7 +5,7 @@
#include "PgConstraint.h"
#include "Pgsql_declare.h"
#include <vector>
#include <boost/optional.hpp>
#include <optional>
class PgConstraintContainer : public PgContainer<PgConstraint> {
public:
@ -16,7 +16,7 @@ public:
const PgConstraint* getFKeyForTableColumn(Oid relid, int16_t attnum) const;
std::vector<PgConstraint> getConstraintsForRelation(Oid relid) const;
boost::optional<PgConstraint> getPrimaryForRelation(Oid relid) const;
std::optional<PgConstraint> getPrimaryForRelation(Oid relid) const;
protected:
virtual PgConstraint loadElem(const Pgsql::Row &row) override;
};

View file

@ -69,7 +69,7 @@ void CodeBuilder::genStructFields(QTextStream &q, const ColumnDataList &columns)
// Any way at generation time we might want to be able to specify the null handle
// - exception/error return
// - magic value
// - boost::optional
// - std::optional
// - boolean flags
// - null pointer (useful for languages where this has no cost, other cases boolean flags will be more performant)
}

View file

@ -41,7 +41,7 @@ public:
* field often provides enough flexibility.
*/
QString m_prefixWith;
// boost::optional<CharToNumericConversion> m_numericConversion;
// std::optional<CharToNumericConversion> m_numericConversion;
};
#endif // STRINGESCAPERULE_H

View file

@ -6,7 +6,7 @@
#include <libpq-fe.h>
#include "Pgsql_declare.h"
#include "Pgsql_oids.h"
#include <boost/optional.hpp>
#include <optional>
namespace Pgsql {
@ -37,7 +37,7 @@ namespace Pgsql {
Param add(const QString &s, Oid oid=varchar_oid);
Param add(const char *data, Oid oid=varchar_oid);
Param add(boost::optional<std::string> s, Oid oid=varchar_oid)
Param add(std::optional<std::string> s, Oid oid=varchar_oid)
{
return add(s ? s->c_str() : nullptr, oid);
}

View file

@ -8,7 +8,7 @@
#include <vector>
#include <QString>
#include <QDateTime>
#include <boost/optional.hpp>
#include <optional>
namespace Pgsql {
@ -154,10 +154,10 @@ namespace Pgsql {
};
template <typename T>
void operator<<(boost::optional<T> &s, const Value &v)
void operator<<(std::optional<T> &s, const Value &v)
{
if (v.null())
s = boost::optional<T>();
s = std::optional<T>();
else
*s << v;
}

View file

@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <gmock/gmock-matchers.h>
#include "PasswordManager.h"
//#include "PasswordManager.h"
#include "PrintTo_Qt.h"
using namespace testing;