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);
m_yubiKeyBeingPolled = false;
#endif
if (isVisible()) {
return;
}
clearForms();
}
void DatabaseOpenWidget::load(const QString& filename)
@ -148,8 +154,9 @@ void DatabaseOpenWidget::load(const QString& filename)
void DatabaseOpenWidget::clearForms()
{
m_ui->editPassword->clear();
m_ui->editPassword->setText("");
m_ui->comboKeyFile->clear();
m_ui->comboKeyFile->setEditText("");
m_ui->checkPassword->setChecked(true);
m_ui->checkKeyFile->setChecked(false);
m_ui->checkChallengeResponse->setChecked(false);
@ -225,7 +232,7 @@ void DatabaseOpenWidget::openDatabase()
} else {
m_ui->messageWidget->showMessage(tr("Unable to open the database:\n%1").arg(error),
MessageWidget::Error);
m_ui->editPassword->clear();
m_ui->editPassword->setText("");
#ifdef WITH_XC_TOUCHID
// 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
if (config()->get("SSHAgent", false).toBool()) {
connect(this,
SIGNAL(currentModeChanged(DatabaseWidget::Mode)),
SSHAgent::instance(),
SLOT(databaseModeChanged(DatabaseWidget::Mode)));
connect(this, SIGNAL(closeRequest()), SSHAgent::instance(), SLOT(databaseModeChanged()));
connect(this, SIGNAL(databaseLocked()), SSHAgent::instance(), SLOT(databaseModeChanged()));
connect(this, SIGNAL(databaseUnlocked()), SSHAgent::instance(), SLOT(databaseModeChanged()));
}
#endif
@ -1147,7 +1144,7 @@ bool DatabaseWidget::lock()
if (!m_db->save(nullptr, false, false)) {
return false;
}
} else if (isLocked()) {
} else if (!isLocked()) {
QString msg;
if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) {
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());
} else {
// This fix a bug when the QLineEdit is disabled while switching config
if (isEnabled() == false) {
if (!isEnabled()) {
setEnabled(true);
setReadOnly(false);
}

View File

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

View File

@ -1,5 +1,3 @@
#include <utility>
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
@ -443,6 +441,8 @@ void EditEntryWidget::updateSSHAgentKeyInfo()
if (SSHAgent::instance()->isAgentRunning()) {
m_sshAgentUi->addToAgentButton->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->publicKeyEdit->document()->setPlainText(key.publicKey());
quint32 lifetime = 0;
int lifetime = 0;
bool confirm = m_sshAgentUi->requireUserConfirmationCheckBox->isChecked();
if (m_sshAgentUi->lifetimeCheckBox->isChecked()) {
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);
return;
}
if (m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked()) {
SSHAgent::instance()->removeIdentityAtLock(key, m_entry->uuid());
}
}
void EditEntryWidget::removeKeyFromAgent()

View File

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

View File

@ -38,16 +38,17 @@ SSHAgent::SSHAgent(QObject* parent)
SSHAgent::~SSHAgent()
{
for (const QSet<OpenSSHKey>& keys : m_keys.values()) {
for (OpenSSHKey key : keys) {
removeIdentity(key);
}
auto it = m_addedKeys.begin();
while (it != m_addedKeys.end()) {
OpenSSHKey key = it.key();
removeIdentity(key);
it = m_addedKeys.erase(it);
}
}
SSHAgent* SSHAgent::instance()
{
if (m_instance == nullptr) {
if (!m_instance) {
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
}
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()) {
m_error = tr("No agent running, cannot add identity.");
@ -201,9 +211,18 @@ bool SSHAgent::addIdentity(OpenSSHKey& key, quint32 lifetime, bool confirm)
return false;
}
OpenSSHKey keyCopy = key;
keyCopy.clearPrivate();
m_addedKeys[keyCopy] = removeOnLock;
return true;
}
/**
* Remove an identity from the SSH agent.
*
* @param key identity to remove
* @return true on success
*/
bool SSHAgent::removeIdentity(OpenSSHKey& key)
{
if (!isAgentRunning()) {
@ -222,120 +241,121 @@ bool SSHAgent::removeIdentity(OpenSSHKey& key)
request.writeString(keyData);
QByteArray responseData;
if (!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.");
return false;
}
return true;
return sendMessage(requestData, responseData);
}
void SSHAgent::removeIdentityAtLock(const OpenSSHKey& key, const QUuid& uuid)
/**
* Change "remove identity on lock" setting for a key already added to the agent.
* Will to nothing if the key has not been added to the agent.
*
* @param key key to change setting for
* @param autoRemove whether to remove the key from the agent when database is locked
*/
void SSHAgent::setAutoRemoveOnLock(const OpenSSHKey& key, bool autoRemove)
{
OpenSSHKey copy = key;
copy.clearPrivate();
m_keys[uuid].insert(copy);
if (m_addedKeys.contains(key)) {
m_addedKeys[key] = autoRemove;
}
}
void SSHAgent::databaseModeChanged(DatabaseWidget::Mode mode)
void SSHAgent::databaseModeChanged()
{
DatabaseWidget* widget = qobject_cast<DatabaseWidget*>(sender());
if (widget == nullptr) {
auto* widget = qobject_cast<DatabaseWidget*>(sender());
if (!widget) {
return;
}
const QUuid& uuid = widget->database()->uuid();
if (mode == DatabaseWidget::Mode::LockedMode && m_keys.contains(uuid)) {
QSet<OpenSSHKey> keys = m_keys.take(uuid);
for (OpenSSHKey key : keys) {
if (!removeIdentity(key)) {
emit error(m_error);
}
}
} else if (mode == DatabaseWidget::Mode::ViewMode && !m_keys.contains(uuid)) {
for (Entry* e : widget->database()->rootGroup()->entriesRecursive()) {
if (widget->database()->metadata()->recycleBinEnabled()
&& e->group() == widget->database()->metadata()->recycleBin()) {
continue;
}
if (!e->attachments()->hasKey("KeeAgent.settings")) {
continue;
}
KeeAgentSettings settings;
settings.fromXml(e->attachments()->value("KeeAgent.settings"));
if (!settings.allowUseOfSshKey()) {
continue;
}
QByteArray keyData;
QString fileName;
if (settings.selectedType() == "attachment") {
fileName = settings.attachmentName();
keyData = e->attachments()->value(fileName);
} else if (!settings.fileName().isEmpty()) {
QFile file(settings.fileName());
QFileInfo fileInfo(file);
fileName = fileInfo.fileName();
if (file.size() > 1024 * 1024) {
continue;
}
if (!file.open(QIODevice::ReadOnly)) {
continue;
}
keyData = file.readAll();
}
if (keyData.isEmpty()) {
continue;
}
OpenSSHKey key;
if (!key.parse(keyData)) {
continue;
}
if (!key.openPrivateKey(e->password())) {
continue;
}
if (key.comment().isEmpty()) {
key.setComment(e->username());
}
if (key.comment().isEmpty()) {
key.setComment(fileName);
}
if (settings.removeAtDatabaseClose()) {
removeIdentityAtLock(key, uuid);
}
if (settings.addAtDatabaseOpen()) {
int lifetime = 0;
if (settings.useLifetimeConstraintWhenAdding()) {
lifetime = settings.lifetimeConstraintDuration();
}
if (!addIdentity(key, lifetime, settings.useConfirmConstraintWhenAdding())) {
if (widget->isLocked()) {
auto it = m_addedKeys.begin();
while (it != m_addedKeys.end()) {
OpenSSHKey key = it.key();
if (it.value()) {
if (!removeIdentity(key)) {
emit error(m_error);
}
it = m_addedKeys.erase(it);
} else {
// don't remove it yet
m_addedKeys[key] = false;
++it;
}
}
return;
}
for (Entry* e : widget->database()->rootGroup()->entriesRecursive()) {
if (widget->database()->metadata()->recycleBinEnabled()
&& e->group() == widget->database()->metadata()->recycleBin()) {
continue;
}
if (!e->attachments()->hasKey("KeeAgent.settings")) {
continue;
}
KeeAgentSettings settings;
settings.fromXml(e->attachments()->value("KeeAgent.settings"));
if (!settings.allowUseOfSshKey()) {
continue;
}
QByteArray keyData;
QString fileName;
if (settings.selectedType() == "attachment") {
fileName = settings.attachmentName();
keyData = e->attachments()->value(fileName);
} else if (!settings.fileName().isEmpty()) {
QFile file(settings.fileName());
QFileInfo fileInfo(file);
fileName = fileInfo.fileName();
if (file.size() > 1024 * 1024) {
continue;
}
if (!file.open(QIODevice::ReadOnly)) {
continue;
}
keyData = file.readAll();
}
if (keyData.isEmpty()) {
continue;
}
OpenSSHKey key;
if (!key.parse(keyData)) {
continue;
}
if (!key.openPrivateKey(e->password())) {
continue;
}
if (key.comment().isEmpty()) {
key.setComment(e->username());
}
if (key.comment().isEmpty()) {
key.setComment(fileName);
}
if (!m_addedKeys.contains(key) && settings.addAtDatabaseOpen()) {
quint32 lifetime = 0;
if (settings.useLifetimeConstraintWhenAdding()) {
lifetime = static_cast<quint32>(settings.lifetimeConstraintDuration());
}
if (!addIdentity(key, settings.removeAtDatabaseClose(),
lifetime, settings.useConfirmConstraintWhenAdding())) {
emit error(m_error);
}
}
}

View File

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