Fix bugs introduced by database refactor #2491 (#2503)

* Fix SSHAgent identity removal on database lock
* Refactor storage and manipulation of SSHAgent keys to streamline process with multiple db's
* Clear password field when widget is hidden, resolves #2502
This commit is contained in:
Janek Bevendorff 2018-11-23 13:49:55 +01:00 committed by Jonathan White
parent d612cad09a
commit 785a64cc3b
8 changed files with 155 additions and 131 deletions

View File

@ -124,6 +124,12 @@ void DatabaseOpenWidget::hideEvent(QHideEvent* event)
disconnect(YubiKey::instance(), nullptr, this, nullptr); disconnect(YubiKey::instance(), nullptr, this, nullptr);
m_yubiKeyBeingPolled = false; m_yubiKeyBeingPolled = false;
#endif #endif
if (isVisible()) {
return;
}
clearForms();
} }
void DatabaseOpenWidget::load(const QString& filename) void DatabaseOpenWidget::load(const QString& filename)
@ -148,8 +154,9 @@ void DatabaseOpenWidget::load(const QString& filename)
void DatabaseOpenWidget::clearForms() void DatabaseOpenWidget::clearForms()
{ {
m_ui->editPassword->clear(); m_ui->editPassword->setText("");
m_ui->comboKeyFile->clear(); m_ui->comboKeyFile->clear();
m_ui->comboKeyFile->setEditText("");
m_ui->checkPassword->setChecked(true); m_ui->checkPassword->setChecked(true);
m_ui->checkKeyFile->setChecked(false); m_ui->checkKeyFile->setChecked(false);
m_ui->checkChallengeResponse->setChecked(false); m_ui->checkChallengeResponse->setChecked(false);
@ -225,7 +232,7 @@ void DatabaseOpenWidget::openDatabase()
} else { } else {
m_ui->messageWidget->showMessage(tr("Unable to open the database:\n%1").arg(error), m_ui->messageWidget->showMessage(tr("Unable to open the database:\n%1").arg(error),
MessageWidget::Error); MessageWidget::Error);
m_ui->editPassword->clear(); m_ui->editPassword->setText("");
#ifdef WITH_XC_TOUCHID #ifdef WITH_XC_TOUCHID
// unable to unlock database, reset TouchID for the current database // unable to unlock database, reset TouchID for the current database

View File

@ -212,11 +212,8 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
#ifdef WITH_XC_SSHAGENT #ifdef WITH_XC_SSHAGENT
if (config()->get("SSHAgent", false).toBool()) { if (config()->get("SSHAgent", false).toBool()) {
connect(this, connect(this, SIGNAL(databaseLocked()), SSHAgent::instance(), SLOT(databaseModeChanged()));
SIGNAL(currentModeChanged(DatabaseWidget::Mode)), connect(this, SIGNAL(databaseUnlocked()), SSHAgent::instance(), SLOT(databaseModeChanged()));
SSHAgent::instance(),
SLOT(databaseModeChanged(DatabaseWidget::Mode)));
connect(this, SIGNAL(closeRequest()), SSHAgent::instance(), SLOT(databaseModeChanged()));
} }
#endif #endif
@ -1147,7 +1144,7 @@ bool DatabaseWidget::lock()
if (!m_db->save(nullptr, false, false)) { if (!m_db->save(nullptr, false, false)) {
return false; return false;
} }
} else if (isLocked()) { } else if (!isLocked()) {
QString msg; QString msg;
if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) { if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) {
msg = tr("\"%1\" was modified.\nSave changes?").arg(m_db->metadata()->name().toHtmlEscaped()); msg = tr("\"%1\" was modified.\nSave changes?").arg(m_db->metadata()->name().toHtmlEscaped());

View File

@ -61,7 +61,7 @@ void PasswordEdit::setShowPassword(bool show)
setText(m_basePasswordEdit->text()); setText(m_basePasswordEdit->text());
} else { } else {
// This fix a bug when the QLineEdit is disabled while switching config // This fix a bug when the QLineEdit is disabled while switching config
if (isEnabled() == false) { if (!isEnabled()) {
setEnabled(true); setEnabled(true);
setReadOnly(false); setReadOnly(false);
} }

View File

@ -20,6 +20,7 @@
#define KEEPASSX_PASSWORDEDIT_H #define KEEPASSX_PASSWORDEDIT_H
#include <QLineEdit> #include <QLineEdit>
#include <QPointer>
class PasswordEdit : public QLineEdit class PasswordEdit : public QLineEdit
{ {
@ -46,7 +47,7 @@ private slots:
private: private:
bool passwordsEqual() const; bool passwordsEqual() const;
PasswordEdit* m_basePasswordEdit; QPointer<PasswordEdit> m_basePasswordEdit;
}; };
#endif // KEEPASSX_PASSWORDEDIT_H #endif // KEEPASSX_PASSWORDEDIT_H

View File

@ -1,5 +1,3 @@
#include <utility>
/* /*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de> * Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
@ -443,6 +441,8 @@ void EditEntryWidget::updateSSHAgentKeyInfo()
if (SSHAgent::instance()->isAgentRunning()) { if (SSHAgent::instance()->isAgentRunning()) {
m_sshAgentUi->addToAgentButton->setEnabled(true); m_sshAgentUi->addToAgentButton->setEnabled(true);
m_sshAgentUi->removeFromAgentButton->setEnabled(true); m_sshAgentUi->removeFromAgentButton->setEnabled(true);
SSHAgent::instance()->setAutoRemoveOnLock(key, m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked());
} }
} }
@ -558,21 +558,18 @@ void EditEntryWidget::addKeyToAgent()
m_sshAgentUi->commentTextLabel->setText(key.comment()); m_sshAgentUi->commentTextLabel->setText(key.comment());
m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey()); m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey());
quint32 lifetime = 0; int lifetime = 0;
bool confirm = m_sshAgentUi->requireUserConfirmationCheckBox->isChecked(); bool confirm = m_sshAgentUi->requireUserConfirmationCheckBox->isChecked();
if (m_sshAgentUi->lifetimeCheckBox->isChecked()) { if (m_sshAgentUi->lifetimeCheckBox->isChecked()) {
lifetime = m_sshAgentUi->lifetimeSpinBox->value(); lifetime = m_sshAgentUi->lifetimeSpinBox->value();
} }
if (!SSHAgent::instance()->addIdentity(key, lifetime, confirm)) { if (!SSHAgent::instance()->addIdentity(key, m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked(),
static_cast<quint32>(lifetime), confirm)) {
showMessage(SSHAgent::instance()->errorString(), MessageWidget::Error); showMessage(SSHAgent::instance()->errorString(), MessageWidget::Error);
return; return;
} }
if (m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked()) {
SSHAgent::instance()->removeIdentityAtLock(key, m_entry->uuid());
}
} }
void EditEntryWidget::removeKeyFromAgent() void EditEntryWidget::removeKeyFromAgent()

View File

@ -22,6 +22,7 @@
#include <QModelIndex> #include <QModelIndex>
#include <QScopedPointer> #include <QScopedPointer>
#include <QButtonGroup> #include <QButtonGroup>
#include <QPointer>
#include "config-keepassx.h" #include "config-keepassx.h"
#include "gui/EditWidget.h" #include "gui/EditWidget.h"

View File

@ -38,16 +38,17 @@ SSHAgent::SSHAgent(QObject* parent)
SSHAgent::~SSHAgent() SSHAgent::~SSHAgent()
{ {
for (const QSet<OpenSSHKey>& keys : m_keys.values()) { auto it = m_addedKeys.begin();
for (OpenSSHKey key : keys) { while (it != m_addedKeys.end()) {
OpenSSHKey key = it.key();
removeIdentity(key); removeIdentity(key);
} it = m_addedKeys.erase(it);
} }
} }
SSHAgent* SSHAgent::instance() SSHAgent* SSHAgent::instance()
{ {
if (m_instance == nullptr) { if (!m_instance) {
qFatal("Race condition: instance wanted before it was initialized, this is a bug."); qFatal("Race condition: instance wanted before it was initialized, this is a bug.");
} }
@ -159,7 +160,16 @@ bool SSHAgent::sendMessage(const QByteArray& in, QByteArray& out)
#endif #endif
} }
bool SSHAgent::addIdentity(OpenSSHKey& key, quint32 lifetime, bool confirm) /**
* Add the identity to the SSH agent.
*
* @param key identity / key to add
* @param lifetime time after which the key should expire
* @param confirm ask for confirmation before adding the key
* @param removeOnLock autoremove from agent when the Database is locked
* @return true on success
*/
bool SSHAgent::addIdentity(OpenSSHKey& key, bool removeOnLock, quint32 lifetime, bool confirm)
{ {
if (!isAgentRunning()) { if (!isAgentRunning()) {
m_error = tr("No agent running, cannot add identity."); m_error = tr("No agent running, cannot add identity.");
@ -201,9 +211,18 @@ bool SSHAgent::addIdentity(OpenSSHKey& key, quint32 lifetime, bool confirm)
return false; return false;
} }
OpenSSHKey keyCopy = key;
keyCopy.clearPrivate();
m_addedKeys[keyCopy] = removeOnLock;
return true; return true;
} }
/**
* Remove an identity from the SSH agent.
*
* @param key identity to remove
* @return true on success
*/
bool SSHAgent::removeIdentity(OpenSSHKey& key) bool SSHAgent::removeIdentity(OpenSSHKey& key)
{ {
if (!isAgentRunning()) { if (!isAgentRunning()) {
@ -222,44 +241,49 @@ bool SSHAgent::removeIdentity(OpenSSHKey& key)
request.writeString(keyData); request.writeString(keyData);
QByteArray responseData; QByteArray responseData;
if (!sendMessage(requestData, responseData)) { return sendMessage(requestData, responseData);
return false;
} }
if (responseData.length() < 1 || static_cast<quint8>(responseData[0]) != SSH_AGENT_SUCCESS) { /**
m_error = tr("Agent does not have this identity."); * Change "remove identity on lock" setting for a key already added to the agent.
return false; * Will to nothing if the key has not been added to the agent.
} *
* @param key key to change setting for
return true; * @param autoRemove whether to remove the key from the agent when database is locked
} */
void SSHAgent::setAutoRemoveOnLock(const OpenSSHKey& key, bool autoRemove)
void SSHAgent::removeIdentityAtLock(const OpenSSHKey& key, const QUuid& uuid)
{ {
OpenSSHKey copy = key; if (m_addedKeys.contains(key)) {
copy.clearPrivate(); m_addedKeys[key] = autoRemove;
m_keys[uuid].insert(copy); }
} }
void SSHAgent::databaseModeChanged(DatabaseWidget::Mode mode) void SSHAgent::databaseModeChanged()
{ {
DatabaseWidget* widget = qobject_cast<DatabaseWidget*>(sender()); auto* widget = qobject_cast<DatabaseWidget*>(sender());
if (!widget) {
if (widget == nullptr) {
return; return;
} }
const QUuid& uuid = widget->database()->uuid(); if (widget->isLocked()) {
auto it = m_addedKeys.begin();
if (mode == DatabaseWidget::Mode::LockedMode && m_keys.contains(uuid)) { while (it != m_addedKeys.end()) {
OpenSSHKey key = it.key();
QSet<OpenSSHKey> keys = m_keys.take(uuid); if (it.value()) {
for (OpenSSHKey key : keys) {
if (!removeIdentity(key)) { if (!removeIdentity(key)) {
emit error(m_error); emit error(m_error);
} }
it = m_addedKeys.erase(it);
} else {
// don't remove it yet
m_addedKeys[key] = false;
++it;
} }
} else if (mode == DatabaseWidget::Mode::ViewMode && !m_keys.contains(uuid)) { }
return;
}
for (Entry* e : widget->database()->rootGroup()->entriesRecursive()) { for (Entry* e : widget->database()->rootGroup()->entriesRecursive()) {
if (widget->database()->metadata()->recycleBinEnabled() if (widget->database()->metadata()->recycleBinEnabled()
@ -322,21 +346,17 @@ void SSHAgent::databaseModeChanged(DatabaseWidget::Mode mode)
key.setComment(fileName); key.setComment(fileName);
} }
if (settings.removeAtDatabaseClose()) { if (!m_addedKeys.contains(key) && settings.addAtDatabaseOpen()) {
removeIdentityAtLock(key, uuid); quint32 lifetime = 0;
}
if (settings.addAtDatabaseOpen()) {
int lifetime = 0;
if (settings.useLifetimeConstraintWhenAdding()) { if (settings.useLifetimeConstraintWhenAdding()) {
lifetime = settings.lifetimeConstraintDuration(); lifetime = static_cast<quint32>(settings.lifetimeConstraintDuration());
} }
if (!addIdentity(key, lifetime, settings.useConfirmConstraintWhenAdding())) { if (!addIdentity(key, settings.removeAtDatabaseClose(),
lifetime, settings.useConfirmConstraintWhenAdding())) {
emit error(m_error); emit error(m_error);
} }
} }
} }
} }
}

View File

@ -21,6 +21,7 @@
#include "OpenSSHKey.h" #include "OpenSSHKey.h"
#include <QList> #include <QList>
#include <QHash>
#include <QtCore> #include <QtCore>
#include "gui/DatabaseWidget.h" #include "gui/DatabaseWidget.h"
@ -35,15 +36,15 @@ public:
const QString errorString() const; const QString errorString() const;
bool isAgentRunning() const; bool isAgentRunning() const;
bool addIdentity(OpenSSHKey& key, quint32 lifetime = 0, bool confirm = false); bool addIdentity(OpenSSHKey& key, bool removeOnLock, quint32 lifetime, bool confirm);
bool removeIdentity(OpenSSHKey& key); bool removeIdentity(OpenSSHKey& key);
void removeIdentityAtLock(const OpenSSHKey& key, const QUuid& uuid); void setAutoRemoveOnLock(const OpenSSHKey& key, bool autoRemove);
signals: signals:
void error(const QString& message); void error(const QString& message);
public slots: public slots:
void databaseModeChanged(DatabaseWidget::Mode mode = DatabaseWidget::Mode::LockedMode); void databaseModeChanged();
private: private:
const quint8 SSH_AGENT_FAILURE = 5; const quint8 SSH_AGENT_FAILURE = 5;
@ -71,7 +72,7 @@ private:
const quint32 AGENT_COPYDATA_ID = 0x804e50ba; const quint32 AGENT_COPYDATA_ID = 0x804e50ba;
#endif #endif
QMap<QUuid, QSet<OpenSSHKey>> m_keys; QHash<OpenSSHKey, bool> m_addedKeys;
QString m_error; QString m_error;
}; };