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

@ -159,6 +159,18 @@ 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)
{ {
if (m_cryptoEngine) { if (m_cryptoEngine) {
@ -297,3 +309,4 @@ KeyStrengthener PasswordManager::createKeyStrengthener()
key_size key_size
); );
} }

View file

@ -53,6 +53,8 @@ public:
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 encrypt(const std::string &id, const std::string &passwd);
std::string decrypt(const std::string &id, const std::string_view &encpwd); std::string decrypt(const std::string &id, const std::string_view &encpwd);

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>
@ -157,11 +159,13 @@ 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())); cc.setPassword(QString::fromUtf8(pw.data(), pw.size()));
return true; return true;
} }
@ -174,12 +178,13 @@ bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc)
dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str)); dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str));
int exec_result = dlg->exec(); int exec_result = dlg->exec();
if (exec_result == QDialog::Accepted) { if (exec_result == QDialog::Accepted)
{
auto password = dlg->password(); auto password = dlg->password();
cc.setPassword(password); cc.setPassword(password);
if (dlg->saveChecked()) { if (dlg->saveChecked())
encryptPassword(cc); encryptPassword(cc);
}
return true; return true;
} }
return false; return false;
@ -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()) if (!m_passwordManager->locked())
return true; 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

@ -354,6 +354,16 @@ 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
{ {
int group_idx = -1, connection_idx = -1; int group_idx = -1, connection_idx = -1;

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.