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
{
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)
@ -246,7 +258,7 @@ bool PasswordManager::isPskStoreInitialized(QSqlDatabase& db)
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()) {
return false;
}
@ -297,3 +309,4 @@ KeyStrengthener PasswordManager::createKeyStrengthener()
key_size
);
}

View file

@ -14,64 +14,66 @@
namespace Botan {
class Encrypted_PSK_Database;
class PasswordHash;
class Encrypted_PSK_Database;
class PasswordHash;
}
class PasswordManagerException: public std::exception {
public:
using std::exception::exception; //(char const* const _Message);
using std::exception::exception; //(char const* const _Message);
};
class PasswordManagerLockedException: public PasswordManagerException {
public:
using PasswordManagerException::PasswordManagerException;
using PasswordManagerException::PasswordManagerException;
};
class PasswordCryptoEngine;
class PasswordManager {
public:
enum Result {
Ok,
Locked,
Error
};
enum Result {
Ok,
Locked,
Error
};
PasswordManager();
~PasswordManager();
PasswordManager();
~PasswordManager();
/** 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);
/// Opens the PSK database
bool openDatabase(QSqlDatabase &db, QString passphrase);
void closeDatabase();
bool locked() const;
/** 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);
/// Opens the PSK database
bool openDatabase(QSqlDatabase &db, QString passphrase);
void closeDatabase();
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:
QString m_passwordTableName = "psk_passwd";
QString m_secretAlgoTableName = "psk_masterkey_algo";
QString m_secretHashTableName = "psk_masterkey_hash";
std::unique_ptr<PasswordCryptoEngine> m_cryptoEngine;
QString m_passwordTableName = "psk_passwd";
QString m_secretAlgoTableName = "psk_masterkey_algo";
QString m_secretHashTableName = "psk_masterkey_hash";
std::unique_ptr<PasswordCryptoEngine> m_cryptoEngine;
bool isPskStoreInitialized(QSqlDatabase& db);
void initializeNewPskStore(QSqlDatabase &db);
bool isPskStoreInitialized(QSqlDatabase& db);
void initializeNewPskStore(QSqlDatabase &db);
/// Get PasswordHash from parameters in database
KeyStrengthener getKeyStrengthener(QSqlDatabase &db);
KeyStrengthener createKeyStrengthener();
/// 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);
std::tuple<Botan::secure_vector<uint8_t>, Botan::secure_vector<uint8_t>>
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::
:maxdepth: 2
:maxdepth: 3
:caption: Contents:
releasenotes
installation
connectionmanager/index
internals
Indices and tables
==================

View file

@ -8,9 +8,11 @@ Currently only binaries for Windows 64-bit are provided.
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
`C:\\Program files\\pgLab`. You can run the pgLab.exe from there. If it is complaining
about missing files this is probably because the required 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>`_.
Unpack the contents of the 7zip archive to a folder of your choosing for
instance `C:\\Program files\\pgLab`. You can run the pgLab.exe from there. If
it is complaining about missing files this is probably because the required
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 "BackupDialog.h"
#include "PasswordPromptDialog.h"
#include "ScopeGuard.h"
#include "ConnectionConfigurationWidget.h"
#include <QSqlQuery>
#include <QInputDialog>
#include <QMessageBox>
#include <QTimer>
@ -156,33 +158,36 @@ std::shared_ptr<PasswordManager> ConnectionController::passwordManager()
bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc)
{
auto enc_pwd = cc.encodedPassword();
if (!enc_pwd.isEmpty()) {
std::string pw;
auto enc_pwd = cc.encodedPassword();
if (!enc_pwd.isEmpty())
{
std::string pw;
bool result = retrieveFromPasswordManager(getPskId(cc.uuid()),
std::string_view(enc_pwd.data(), enc_pwd.size()) , pw);
if (result) {
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.
QString str = cc.makeLongDescription();
auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::SaveOption, nullptr);
dlg->setCaption(tr("Connection password prompt"));
dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str));
int exec_result = dlg->exec();
if (result)
{
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.
QString str = cc.makeLongDescription();
auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::SaveOption, nullptr);
dlg->setCaption(tr("Connection password prompt"));
dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str));
int exec_result = dlg->exec();
if (exec_result == QDialog::Accepted) {
auto password = dlg->password();
cc.setPassword(password);
if (dlg->saveChecked()) {
if (exec_result == QDialog::Accepted)
{
auto password = dlg->password();
cc.setPassword(password);
if (dlg->saveChecked())
encryptPassword(cc);
}
return true;
}
return false;
return true;
}
return false;
}
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;
}
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()
{
auto&& user_cfg_db = m_masterController->userConfigDatabase();
if (m_passwordManager->initialized(user_cfg_db)) {
if (!m_passwordManager->locked())
return true;
auto&& user_cfg_db = m_masterController->userConfigDatabase();
if (m_passwordManager->initialized(user_cfg_db))
{
if (!m_passwordManager->locked())
return true;
while (true) {
// ask user for passphrase
while (true)
{
PassphraseResult pp_result = PassphrasePrompt();
if (!pp_result.success)
break; // leave this retry loop
break;
// user gave OK, if succeeds return true otherwise loop a prompt for password again.
if (m_passwordManager->openDatabase(user_cfg_db, pp_result.passphrase)) {
// user gave OK, if succeeds return true otherwise loop a prompt for password again.
if (m_passwordManager->openDatabase(user_cfg_db, pp_result.passphrase))
{
setRelockTimer(pp_result.rememberForMinutes);
return true;
}
}
}
else {
return true;
}
}
}
else
InitializePasswordManager();
}
return false;
}

View file

@ -12,6 +12,7 @@ class ConnectionTreeModel;
class ConnectionManagerWindow;
class PasswordManager;
class QTimer;
class QSqlDatabase;
class PassphraseResult {
@ -58,6 +59,7 @@ public:
bool UnlockPasswordManagerIfNeeded();
bool decodeConnectionPassword(QUuid id, QByteArray encoded, QString &out_password);
void resetPasswordManager();
private:
MasterController *m_masterController;
ConnectionList *m_connectionList = nullptr;
@ -71,7 +73,6 @@ private:
std::shared_ptr<PasswordManager> m_passwordManager;
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.
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)
{
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

View file

@ -59,6 +59,7 @@ public:
/** Save changed config, group is not allowed to change
*/
void save(const ConnectionConfig &cc);
void clearAllPasswords();
/// Create a new group in the DB and place in the tree
std::variant<int, QSqlError> addGroup(const QString &group_name);
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"));
}
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_actionReset_password_manager_triggered();
private:
Ui::ConnectionManagerWindow *ui;
MasterController *m_masterController;

View file

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

View file

@ -45,6 +45,7 @@ public:
using Connections = QVector<std::shared_ptr<ConnectionConfig>>;
const Connections& connections() const { return m_connections; }
Connections& connections() { return m_connections; }
void erase(int idx, int count = 1);
/// 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

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.