From 4fa2189b273a8d1324dd35c48a4e7dfcec48fb5a Mon Sep 17 00:00:00 2001 From: eelke Date: Mon, 5 Sep 2022 07:33:08 +0200 Subject: [PATCH] Added the capability to reset the password manager Also some documentation about the password manager. --- core/PasswordManager.cpp | 17 ++- core/PasswordManager.h | 72 +++++++------ docs/connectionmanager/index.rst | 7 ++ docs/connectionmanager/passwordmanager.rst | 35 ++++++ docs/index.rst | 4 +- docs/installation.rst | 12 ++- docs/internals.rst | 11 ++ pglab/ConnectionController.cpp | 100 +++++++++++------- pglab/ConnectionController.h | 3 +- pglab/ConnectionListModel.cpp | 12 ++- pglab/ConnectionListModel.h | 1 + pglab/ConnectionManagerWindow.cpp | 12 +++ pglab/ConnectionManagerWindow.h | 2 + pglab/ConnectionManagerWindow.ui | 16 ++- pglablib/ConnectionConfig.h | 1 + releasenotes/config.yaml | 8 ++ ...eset-master-password-6a8f5ccf5a052344.yaml | 5 + 17 files changed, 233 insertions(+), 85 deletions(-) create mode 100644 docs/connectionmanager/index.rst create mode 100644 docs/connectionmanager/passwordmanager.rst create mode 100644 docs/internals.rst create mode 100644 releasenotes/notes/reset-master-password-6a8f5ccf5a052344.yaml diff --git a/core/PasswordManager.cpp b/core/PasswordManager.cpp index 1e3eb1e..b398ba1 100644 --- a/core/PasswordManager.cpp +++ b/core/PasswordManager.cpp @@ -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 ); } + diff --git a/core/PasswordManager.h b/core/PasswordManager.h index 895cdc4..cf079d2 100644 --- a/core/PasswordManager.h +++ b/core/PasswordManager.h @@ -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 m_cryptoEngine; + QString m_passwordTableName = "psk_passwd"; + QString m_secretAlgoTableName = "psk_masterkey_algo"; + QString m_secretHashTableName = "psk_masterkey_hash"; + std::unique_ptr 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> - deriveKey(KeyStrengthener &ks, QString passphrase); + std::tuple, Botan::secure_vector> + deriveKey(KeyStrengthener &ks, QString passphrase); }; diff --git a/docs/connectionmanager/index.rst b/docs/connectionmanager/index.rst new file mode 100644 index 0000000..909e32f --- /dev/null +++ b/docs/connectionmanager/index.rst @@ -0,0 +1,7 @@ +================== +Connection Manager +================== + +.. toctree:: + + passwordmanager diff --git a/docs/connectionmanager/passwordmanager.rst b/docs/connectionmanager/passwordmanager.rst new file mode 100644 index 0000000..e7ad716 --- /dev/null +++ b/docs/connectionmanager/passwordmanager.rst @@ -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. diff --git a/docs/index.rst b/docs/index.rst index ff59f1d..cf75cbd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,11 +7,13 @@ pgLab User Manual ==================================================== .. toctree:: - :maxdepth: 2 + :maxdepth: 3 :caption: Contents: releasenotes installation + connectionmanager/index + internals Indices and tables ================== diff --git a/docs/installation.rst b/docs/installation.rst index 9a416a9..a7d69ac 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -8,9 +8,11 @@ Currently only binaries for Windows 64-bit are provided. Windows ------- -Downloads can be found `here `_. +Downloads can be found `here +`_. -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 `_. +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 `_. diff --git a/docs/internals.rst b/docs/internals.rst new file mode 100644 index 0000000..161e571 --- /dev/null +++ b/docs/internals.rst @@ -0,0 +1,11 @@ +========= +Internals +========= + +--------- +User data +--------- + +All connection configuration information is stored in +`%AppData%\pglab\pglab\pglabuser.db` this is an sqlite database. + diff --git a/pglab/ConnectionController.cpp b/pglab/ConnectionController.cpp index c3ae0c5..93b4d24 100644 --- a/pglab/ConnectionController.cpp +++ b/pglab/ConnectionController.cpp @@ -6,7 +6,9 @@ #include "DatabaseWindow.h" #include "BackupDialog.h" #include "PasswordPromptDialog.h" +#include "ScopeGuard.h" #include "ConnectionConfigurationWidget.h" +#include #include #include #include @@ -156,33 +158,36 @@ std::shared_ptr 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::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::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; } diff --git a/pglab/ConnectionController.h b/pglab/ConnectionController.h index 71930a5..b0ebc99 100644 --- a/pglab/ConnectionController.h +++ b/pglab/ConnectionController.h @@ -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 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); diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index 4cac885..ebec31d 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -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 ConnectionTreeModel::findConfig(const QUuid uuid) const diff --git a/pglab/ConnectionListModel.h b/pglab/ConnectionListModel.h index 39f8764..6ea9919 100644 --- a/pglab/ConnectionListModel.h +++ b/pglab/ConnectionListModel.h @@ -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 addGroup(const QString &group_name); std::optional removeGroup(int row); diff --git a/pglab/ConnectionManagerWindow.cpp b/pglab/ConnectionManagerWindow.cpp index 5377631..4411663 100644 --- a/pglab/ConnectionManagerWindow.cpp +++ b/pglab/ConnectionManagerWindow.cpp @@ -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(); +} + diff --git a/pglab/ConnectionManagerWindow.h b/pglab/ConnectionManagerWindow.h index 680915d..4c70882 100644 --- a/pglab/ConnectionManagerWindow.h +++ b/pglab/ConnectionManagerWindow.h @@ -43,6 +43,8 @@ private slots: void on_actionManual_triggered(); + void on_actionReset_password_manager_triggered(); + private: Ui::ConnectionManagerWindow *ui; MasterController *m_masterController; diff --git a/pglab/ConnectionManagerWindow.ui b/pglab/ConnectionManagerWindow.ui index 2d52b88..6404966 100644 --- a/pglab/ConnectionManagerWindow.ui +++ b/pglab/ConnectionManagerWindow.ui @@ -33,11 +33,18 @@ Fi&le - - + + + + Help + + + + + @@ -181,6 +188,11 @@ QToolButton { Manual + + + Reset password manager + + diff --git a/pglablib/ConnectionConfig.h b/pglablib/ConnectionConfig.h index 5e5a9c4..ff8c1ff 100644 --- a/pglablib/ConnectionConfig.h +++ b/pglablib/ConnectionConfig.h @@ -45,6 +45,7 @@ public: using Connections = QVector>; 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. diff --git a/releasenotes/config.yaml b/releasenotes/config.yaml index fa9f8f3..54fcadc 100644 --- a/releasenotes/config.yaml +++ b/releasenotes/config.yaml @@ -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 diff --git a/releasenotes/notes/reset-master-password-6a8f5ccf5a052344.yaml b/releasenotes/notes/reset-master-password-6a8f5ccf5a052344.yaml new file mode 100644 index 0000000..08ca7f4 --- /dev/null +++ b/releasenotes/notes/reset-master-password-6a8f5ccf5a052344.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Added menu option to reset the password managers master password. + This will ofcourse also clear all saved passwords.