Added the capability to reset the password manager

Also some documentation about the password manager.
This commit is contained in:
eelke 2022-09-05 07:33:08 +02:00
parent f8528143ac
commit 4fa2189b27
17 changed files with 233 additions and 85 deletions

View file

@ -156,7 +156,19 @@ void PasswordManager::closeDatabase()
bool PasswordManager::locked() const bool PasswordManager::locked() const
{ {
return m_cryptoEngine == nullptr; return m_cryptoEngine == nullptr;
}
void PasswordManager::resetMasterPassword(QSqlDatabase &db)
{
if (!isPskStoreInitialized(db))
return;
closeDatabase();
QSqlQuery del_algo("DELETE FROM " + m_secretAlgoTableName + " WHERE id=1", db);
del_algo.exec();
QSqlQuery del_hash("DELETE FROM " + m_secretHashTableName + " WHERE id=1", db);
del_hash.exec();
} }
std::string PasswordManager::encrypt(const std::string &name, const std::string &passwd) std::string PasswordManager::encrypt(const std::string &name, const std::string &passwd)
@ -246,7 +258,7 @@ bool PasswordManager::isPskStoreInitialized(QSqlDatabase& db)
return false; return false;
} }
QSqlQuery sel_algo("SELECT algo FROM " + m_secretAlgoTableName + " WHERE id=1", db); QSqlQuery sel_algo("SELECT algo FROM " + m_secretAlgoTableName + " WHERE id=1", db);
if (!sel_algo.next()) { if (!sel_algo.next()) {
return false; return false;
} }
@ -297,3 +309,4 @@ KeyStrengthener PasswordManager::createKeyStrengthener()
key_size key_size
); );
} }

View file

@ -14,64 +14,66 @@
namespace Botan { namespace Botan {
class Encrypted_PSK_Database; class Encrypted_PSK_Database;
class PasswordHash; class PasswordHash;
} }
class PasswordManagerException: public std::exception { class PasswordManagerException: public std::exception {
public: public:
using std::exception::exception; //(char const* const _Message); using std::exception::exception; //(char const* const _Message);
}; };
class PasswordManagerLockedException: public PasswordManagerException { class PasswordManagerLockedException: public PasswordManagerException {
public: public:
using PasswordManagerException::PasswordManagerException; using PasswordManagerException::PasswordManagerException;
}; };
class PasswordCryptoEngine; class PasswordCryptoEngine;
class PasswordManager { class PasswordManager {
public: public:
enum Result { enum Result {
Ok, Ok,
Locked, Locked,
Error Error
}; };
PasswordManager(); PasswordManager();
~PasswordManager(); ~PasswordManager();
/** Check if it has been initialized before. /** Check if it has been initialized before.
* *
* If returns false then use createDatabase to set it up * If returns false then use createDatabase to set it up
* else use openDatabase to get access. * else use openDatabase to get access.
*/ */
bool initialized(QSqlDatabase &db); bool initialized(QSqlDatabase &db);
bool createDatabase(QSqlDatabase &db, QString passphrase); bool createDatabase(QSqlDatabase &db, QString passphrase);
/// Opens the PSK database /// Opens the PSK database
bool openDatabase(QSqlDatabase &db, QString passphrase); bool openDatabase(QSqlDatabase &db, QString passphrase);
void closeDatabase(); void closeDatabase();
bool locked() const; bool locked() const;
void resetMasterPassword(QSqlDatabase &db);
std::string encrypt(const std::string &id, const std::string &passwd);
std::string decrypt(const std::string &id, const std::string_view &encpwd); std::string encrypt(const std::string &id, const std::string &passwd);
std::string decrypt(const std::string &id, const std::string_view &encpwd);
private: private:
QString m_passwordTableName = "psk_passwd"; QString m_passwordTableName = "psk_passwd";
QString m_secretAlgoTableName = "psk_masterkey_algo"; QString m_secretAlgoTableName = "psk_masterkey_algo";
QString m_secretHashTableName = "psk_masterkey_hash"; QString m_secretHashTableName = "psk_masterkey_hash";
std::unique_ptr<PasswordCryptoEngine> m_cryptoEngine; std::unique_ptr<PasswordCryptoEngine> m_cryptoEngine;
bool isPskStoreInitialized(QSqlDatabase& db); bool isPskStoreInitialized(QSqlDatabase& db);
void initializeNewPskStore(QSqlDatabase &db); void initializeNewPskStore(QSqlDatabase &db);
/// Get PasswordHash from parameters in database /// Get PasswordHash from parameters in database
KeyStrengthener getKeyStrengthener(QSqlDatabase &db); KeyStrengthener getKeyStrengthener(QSqlDatabase &db);
KeyStrengthener createKeyStrengthener(); KeyStrengthener createKeyStrengthener();
std::tuple<Botan::secure_vector<uint8_t>, Botan::secure_vector<uint8_t>> std::tuple<Botan::secure_vector<uint8_t>, Botan::secure_vector<uint8_t>>
deriveKey(KeyStrengthener &ks, QString passphrase); deriveKey(KeyStrengthener &ks, QString passphrase);
}; };

View file

@ -0,0 +1,7 @@
==================
Connection Manager
==================
.. toctree::
passwordmanager

View file

@ -0,0 +1,35 @@
================
Password manager
================
The connection manager has a build-in password manager which uses encryption
to savely store passwords. If you want to use it is up to you. Check the Save
password checkbox and the password manager will be used to safely store the
password. The first time this happens you will be asked for a password you wish
to use for the password manager. Make sure to remember this password cause there
is no way to recover it if you loose it.
When you try to open a connection for which the password was saved the program
will prompt you for the password managers password. You can choose how long the
program will keep the password manager unlocked. If you cancel this password
prompt the program will prompt you for the password of the database connection.
------------------------------
Resetting the password manager
------------------------------
Unfortunatly at this time there is no way to change the master password.
Neither is it possible to completely reset the password manager other then
removing the `pglabuser.db` file.
--------
Security
--------
To make it hard to brute force decode the saved passwords the passwords are not
simply encrypted with the master password. Instead a keystrengthening algorithm
is applied to your master password combined with a salt. The exact parameters
choosen for keystrengthening depends on the speed of your computer. This
strengthened key will be used only as a basis for encryption. Each password
saved is associated with a random guid, this 128-bit value is combined with
your strengthened key to create the key to save the password.

View file

@ -7,11 +7,13 @@ pgLab User Manual
==================================================== ====================================================
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 3
:caption: Contents: :caption: Contents:
releasenotes releasenotes
installation installation
connectionmanager/index
internals
Indices and tables Indices and tables
================== ==================

View file

@ -8,9 +8,11 @@ Currently only binaries for Windows 64-bit are provided.
Windows Windows
------- -------
Downloads can be found `here <https://eelkeklein.stackstorage.com/s/E9xkMGQDFjHv5XN3>`_. Downloads can be found `here
<https://eelkeklein.stackstorage.com/s/E9xkMGQDFjHv5XN3>`_.
Unpack the contents of the 7zip archive to a folder of your choosing for instance Unpack the contents of the 7zip archive to a folder of your choosing for
`C:\\Program files\\pgLab`. You can run the pgLab.exe from there. If it is complaining instance `C:\\Program files\\pgLab`. You can run the pgLab.exe from there. If
about missing files this is probably because the required Visual C++ Runtime has it is complaining about missing files this is probably because the required
not yet been installed on your machine you can get it from `microsoft <https://aka.ms/vs/17/release/vc_redist.x64.exe>`_. Visual C++ Runtime has not yet been installed on your machine you can get it
from `microsoft <https://aka.ms/vs/17/release/vc_redist.x64.exe>`_.

11
docs/internals.rst Normal file
View file

@ -0,0 +1,11 @@
=========
Internals
=========
---------
User data
---------
All connection configuration information is stored in
`%AppData%\pglab\pglab\pglabuser.db` this is an sqlite database.

View file

@ -6,7 +6,9 @@
#include "DatabaseWindow.h" #include "DatabaseWindow.h"
#include "BackupDialog.h" #include "BackupDialog.h"
#include "PasswordPromptDialog.h" #include "PasswordPromptDialog.h"
#include "ScopeGuard.h"
#include "ConnectionConfigurationWidget.h" #include "ConnectionConfigurationWidget.h"
#include <QSqlQuery>
#include <QInputDialog> #include <QInputDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QTimer> #include <QTimer>
@ -156,33 +158,36 @@ std::shared_ptr<PasswordManager> ConnectionController::passwordManager()
bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc) bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc)
{ {
auto enc_pwd = cc.encodedPassword(); auto enc_pwd = cc.encodedPassword();
if (!enc_pwd.isEmpty()) { if (!enc_pwd.isEmpty())
std::string pw; {
std::string pw;
bool result = retrieveFromPasswordManager(getPskId(cc.uuid()), bool result = retrieveFromPasswordManager(getPskId(cc.uuid()),
std::string_view(enc_pwd.data(), enc_pwd.size()) , pw); std::string_view(enc_pwd.data(), enc_pwd.size()) , pw);
if (result) { if (result)
cc.setPassword(QString::fromUtf8(pw.data(), pw.size())); {
return true; cc.setPassword(QString::fromUtf8(pw.data(), pw.size()));
} return true;
} }
// Geen else hier want als voorgaande blok niet geretourneerd heeft moeten we wachtwoord }
// ook aan de gebruiker vragen zoals hier gebeurd. // Geen else hier want als voorgaande blok niet geretourneerd heeft moeten we wachtwoord
QString str = cc.makeLongDescription(); // ook aan de gebruiker vragen zoals hier gebeurd.
auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::SaveOption, nullptr); QString str = cc.makeLongDescription();
dlg->setCaption(tr("Connection password prompt")); auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::SaveOption, nullptr);
dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str)); dlg->setCaption(tr("Connection password prompt"));
int exec_result = dlg->exec(); dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str));
int exec_result = dlg->exec();
if (exec_result == QDialog::Accepted) { if (exec_result == QDialog::Accepted)
auto password = dlg->password(); {
cc.setPassword(password); auto password = dlg->password();
if (dlg->saveChecked()) { cc.setPassword(password);
if (dlg->saveChecked())
encryptPassword(cc); encryptPassword(cc);
}
return true; return true;
} }
return false; return false;
} }
bool ConnectionController::retrieveFromPasswordManager(const std::string &password_id, const std::string_view &enc_password, std::string &password) bool ConnectionController::retrieveFromPasswordManager(const std::string &password_id, const std::string_view &enc_password, std::string &password)
@ -224,29 +229,48 @@ bool ConnectionController::decodeConnectionPassword(QUuid id, QByteArray encoded
return res; return res;
} }
void ConnectionController::resetPasswordManager()
{
auto&& user_cfg_db = m_masterController->userConfigDatabase();
user_cfg_db.transaction();
try
{
m_passwordManager->resetMasterPassword(user_cfg_db);
m_connectionTreeModel->clearAllPasswords();
user_cfg_db.commit();
}
catch (...)
{
user_cfg_db.rollback();
throw;
}
}
bool ConnectionController::UnlockPasswordManagerIfNeeded() bool ConnectionController::UnlockPasswordManagerIfNeeded()
{ {
auto&& user_cfg_db = m_masterController->userConfigDatabase(); auto&& user_cfg_db = m_masterController->userConfigDatabase();
if (m_passwordManager->initialized(user_cfg_db)) { if (m_passwordManager->initialized(user_cfg_db))
if (!m_passwordManager->locked()) {
return true; if (!m_passwordManager->locked())
return true;
while (true) { while (true)
// ask user for passphrase {
PassphraseResult pp_result = PassphrasePrompt(); PassphraseResult pp_result = PassphrasePrompt();
if (!pp_result.success) if (!pp_result.success)
break; // leave this retry loop break;
// user gave OK, if succeeds return true otherwise loop a prompt for password again. // user gave OK, if succeeds return true otherwise loop a prompt for password again.
if (m_passwordManager->openDatabase(user_cfg_db, pp_result.passphrase)) { if (m_passwordManager->openDatabase(user_cfg_db, pp_result.passphrase))
{
setRelockTimer(pp_result.rememberForMinutes); setRelockTimer(pp_result.rememberForMinutes);
return true; return true;
} }
} }
} }
else { else
InitializePasswordManager(); InitializePasswordManager();
}
return false; return false;
} }

View file

@ -12,6 +12,7 @@ class ConnectionTreeModel;
class ConnectionManagerWindow; class ConnectionManagerWindow;
class PasswordManager; class PasswordManager;
class QTimer; class QTimer;
class QSqlDatabase;
class PassphraseResult { class PassphraseResult {
@ -58,6 +59,7 @@ public:
bool UnlockPasswordManagerIfNeeded(); bool UnlockPasswordManagerIfNeeded();
bool decodeConnectionPassword(QUuid id, QByteArray encoded, QString &out_password); bool decodeConnectionPassword(QUuid id, QByteArray encoded, QString &out_password);
void resetPasswordManager();
private: private:
MasterController *m_masterController; MasterController *m_masterController;
ConnectionList *m_connectionList = nullptr; ConnectionList *m_connectionList = nullptr;
@ -71,7 +73,6 @@ private:
std::shared_ptr<PasswordManager> m_passwordManager; std::shared_ptr<PasswordManager> m_passwordManager;
bool retrieveFromPasswordManager(const std::string &password_id, const std::string_view &enc_password, std::string &password); bool retrieveFromPasswordManager(const std::string &password_id, const std::string_view &enc_password, std::string &password);
// bool updatePasswordManager(const std::string &password_id, const std::string &password, std::string &enc_password);
/// Expects the plaintext password to be the password that needs encoding. /// Expects the plaintext password to be the password that needs encoding.
bool encryptPassword(ConnectionConfig &cc); bool encryptPassword(ConnectionConfig &cc);

View file

@ -351,7 +351,17 @@ void ConnectionTreeModel::save(const QString &group_name, const ConnectionConfig
void ConnectionTreeModel::save(const ConnectionConfig &cc) void ConnectionTreeModel::save(const ConnectionConfig &cc)
{ {
saveToDb(cc); saveToDb(cc);
}
void ConnectionTreeModel::clearAllPasswords()
{
for (auto group : m_groups)
for (auto cc : group->connections())
{
cc->setEncodedPassword({});
saveToDb(*cc);
}
} }
std::tuple<int, int> ConnectionTreeModel::findConfig(const QUuid uuid) const std::tuple<int, int> ConnectionTreeModel::findConfig(const QUuid uuid) const

View file

@ -59,6 +59,7 @@ public:
/** Save changed config, group is not allowed to change /** Save changed config, group is not allowed to change
*/ */
void save(const ConnectionConfig &cc); void save(const ConnectionConfig &cc);
void clearAllPasswords();
/// Create a new group in the DB and place in the tree /// Create a new group in the DB and place in the tree
std::variant<int, QSqlError> addGroup(const QString &group_name); std::variant<int, QSqlError> addGroup(const QString &group_name);
std::optional<QSqlError> removeGroup(int row); std::optional<QSqlError> removeGroup(int row);

View file

@ -115,3 +115,15 @@ void ConnectionManagerWindow::on_actionManual_triggered()
QDesktopServices::openUrl(QString("https://eelke.gitlab.io/pgLab/#pglab-user-manual")); QDesktopServices::openUrl(QString("https://eelke.gitlab.io/pgLab/#pglab-user-manual"));
} }
void ConnectionManagerWindow::on_actionReset_password_manager_triggered()
{
auto warning_response = QMessageBox::warning(this, "pgLab", tr(
"Warning you are about to reset the password manager master passwords "
"all stored passwords will be lost! Are you shure you wish to continue?"),
QMessageBox::Yes | QMessageBox::No);
if (warning_response == QMessageBox::Yes)
m_connectionController->resetPasswordManager();
}

View file

@ -43,6 +43,8 @@ private slots:
void on_actionManual_triggered(); void on_actionManual_triggered();
void on_actionReset_password_manager_triggered();
private: private:
Ui::ConnectionManagerWindow *ui; Ui::ConnectionManagerWindow *ui;
MasterController *m_masterController; MasterController *m_masterController;

View file

@ -33,11 +33,18 @@
<property name="title"> <property name="title">
<string>Fi&amp;le</string> <string>Fi&amp;le</string>
</property> </property>
<addaction name="actionAbout"/> <addaction name="actionReset_password_manager"/>
<addaction name="actionManual"/>
<addaction name="actionQuit_application"/> <addaction name="actionQuit_application"/>
</widget> </widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionManual"/>
<addaction name="actionAbout"/>
</widget>
<addaction name="menuFile"/> <addaction name="menuFile"/>
<addaction name="menuHelp"/>
</widget> </widget>
<widget class="QStatusBar" name="statusbar"/> <widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar"> <widget class="QToolBar" name="toolBar">
@ -181,6 +188,11 @@ QToolButton {
<string>Manual</string> <string>Manual</string>
</property> </property>
</action> </action>
<action name="actionReset_password_manager">
<property name="text">
<string>Reset password manager</string>
</property>
</action>
</widget> </widget>
<resources> <resources>
<include location="resources.qrc"/> <include location="resources.qrc"/>

View file

@ -45,6 +45,7 @@ public:
using Connections = QVector<std::shared_ptr<ConnectionConfig>>; using Connections = QVector<std::shared_ptr<ConnectionConfig>>;
const Connections& connections() const { return m_connections; } const Connections& connections() const { return m_connections; }
Connections& connections() { return m_connections; }
void erase(int idx, int count = 1); void erase(int idx, int count = 1);
/// Adds cc to the group and returns the index within the group. /// Adds cc to the group and returns the index within the group.

View file

@ -1,2 +1,10 @@
--- ---
sections:
# The prelude section is implicitly included.
- [features, New Features]
- [other, Changes]
- [issues, Known Issues]
- [upgrade, Upgrade Notes]
- [security, Security Issues]
- [fixes, Bug Fixes]
encoding: utf8 encoding: utf8

View file

@ -0,0 +1,5 @@
---
features:
- |
Added menu option to reset the password managers master password.
This will ofcourse also clear all saved passwords.