SSH Agent: Refactor entry and agent key management

- Remove duplicate code to load a key (EditEntryWidget & SSHAgent)
 - Refactor all key loading and saving to KeeAgentSettings
 - Depend only on Entry to allow future CLI expansion
This commit is contained in:
Toni Spets 2019-11-09 20:45:08 +02:00 committed by Jonathan White
parent c97ee5395b
commit e24a858f39
5 changed files with 196 additions and 152 deletions

View File

@ -106,12 +106,8 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
setupAutoType();
#ifdef WITH_XC_SSHAGENT
if (config()->get("SSHAgent", false).toBool()) {
setupSSHAgent();
m_sshAgentEnabled = true;
} else {
m_sshAgentEnabled = false;
}
setupSSHAgent();
m_sshAgentEnabled = config()->get("SSHAgent", false).toBool();
#endif
#ifdef WITH_XC_BROWSER
@ -544,7 +540,7 @@ void EditEntryWidget::setupSSHAgent()
void EditEntryWidget::updateSSHAgent()
{
KeeAgentSettings settings;
settings.fromXml(m_advancedUi->attachmentsWidget->getAttachment("KeeAgent.settings"));
settings.fromEntry(m_entry);
m_sshAgentUi->addKeyToAgentCheckBox->setChecked(settings.addAtDatabaseOpen());
m_sshAgentUi->removeKeyFromAgentCheckBox->setChecked(settings.removeAtDatabaseClose());
@ -557,15 +553,8 @@ void EditEntryWidget::updateSSHAgent()
m_sshAgentUi->copyToClipboardButton->setEnabled(false);
m_sshAgentSettings = settings;
updateSSHAgentAttachments();
if (settings.selectedType() == "attachment") {
m_sshAgentUi->attachmentRadioButton->setChecked(true);
} else {
m_sshAgentUi->externalFileRadioButton->setChecked(true);
}
updateSSHAgentKeyInfo();
}
void EditEntryWidget::updateSSHAgentAttachment()
@ -590,6 +579,14 @@ void EditEntryWidget::updateSSHAgentAttachments()
m_sshAgentUi->attachmentComboBox->setCurrentText(m_sshAgentSettings.attachmentName());
m_sshAgentUi->externalFileEdit->setText(m_sshAgentSettings.fileName());
if (m_sshAgentSettings.selectedType() == "attachment") {
m_sshAgentUi->attachmentRadioButton->setChecked(true);
} else {
m_sshAgentUi->externalFileRadioButton->setChecked(true);
}
updateSSHAgentKeyInfo();
}
void EditEntryWidget::updateSSHAgentKeyInfo()
@ -639,10 +636,8 @@ void EditEntryWidget::updateSSHAgentKeyInfo()
}
}
void EditEntryWidget::saveSSHAgentConfig()
void EditEntryWidget::toKeeAgentSettings(KeeAgentSettings& settings) const
{
KeeAgentSettings settings;
settings.setAddAtDatabaseOpen(m_sshAgentUi->addKeyToAgentCheckBox->isChecked());
settings.setRemoveAtDatabaseClose(m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked());
settings.setUseConfirmConstraintWhenAdding(m_sshAgentUi->requireUserConfirmationCheckBox->isChecked());
@ -662,14 +657,6 @@ void EditEntryWidget::saveSSHAgentConfig()
// we don't use this either but we don't want it to dirty flag the config
settings.setSaveAttachmentToTempFile(m_sshAgentSettings.saveAttachmentToTempFile());
if (settings.isDefault()) {
m_advancedUi->attachmentsWidget->removeAttachment("KeeAgent.settings");
} else if (settings != m_sshAgentSettings) {
m_advancedUi->attachmentsWidget->setAttachment("KeeAgent.settings", settings.toXml());
}
m_sshAgentSettings = settings;
}
void EditEntryWidget::browsePrivateKey()
@ -684,58 +671,18 @@ void EditEntryWidget::browsePrivateKey()
bool EditEntryWidget::getOpenSSHKey(OpenSSHKey& key, bool decrypt)
{
QString fileName;
QByteArray privateKeyData;
KeeAgentSettings settings;
toKeeAgentSettings(settings);
if (m_sshAgentUi->attachmentRadioButton->isChecked()) {
fileName = m_sshAgentUi->attachmentComboBox->currentText();
privateKeyData = m_advancedUi->attachmentsWidget->getAttachment(fileName);
} else {
QFile localFile(m_sshAgentUi->externalFileEdit->text());
QFileInfo localFileInfo(localFile);
fileName = localFileInfo.fileName();
if (localFile.fileName().isEmpty()) {
return false;
}
if (localFile.size() > 1024 * 1024) {
showMessage(tr("File too large to be a private key"), MessageWidget::Error);
return false;
}
if (!localFile.open(QIODevice::ReadOnly)) {
showMessage(tr("Failed to open private key"), MessageWidget::Error);
return false;
}
privateKeyData = localFile.readAll();
}
if (privateKeyData.isEmpty()) {
if (!settings.keyConfigured()) {
return false;
}
if (!key.parsePKCS1PEM(privateKeyData)) {
showMessage(key.errorString(), MessageWidget::Error);
if (!settings.toOpenSSHKey(m_entry, key, decrypt)) {
showMessage(settings.errorString(), MessageWidget::Error);
return false;
}
if (key.encrypted() && (decrypt || key.publicKey().isEmpty())) {
if (!key.openKey(m_entry->password())) {
showMessage(key.errorString(), MessageWidget::Error);
return false;
}
}
if (key.comment().isEmpty()) {
key.setComment(m_entry->username());
}
if (key.comment().isEmpty()) {
key.setComment(fileName);
}
return true;
}
@ -751,11 +698,7 @@ void EditEntryWidget::addKeyToAgent()
m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey());
KeeAgentSettings settings;
settings.setRemoveAtDatabaseClose(m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked());
settings.setUseConfirmConstraintWhenAdding(m_sshAgentUi->requireUserConfirmationCheckBox->isChecked());
settings.setUseLifetimeConstraintWhenAdding(m_sshAgentUi->lifetimeCheckBox->isChecked());
settings.setLifetimeConstraintDuration(m_sshAgentUi->lifetimeSpinBox->value());
toKeeAgentSettings(settings);
if (!SSHAgent::instance()->addIdentity(key, settings)) {
showMessage(SSHAgent::instance()->errorString(), MessageWidget::Error);
@ -1064,9 +1007,7 @@ bool EditEntryWidget::commitEntry()
m_autoTypeAssoc->removeEmpty();
#ifdef WITH_XC_SSHAGENT
if (m_sshAgentEnabled) {
saveSSHAgentConfig();
}
toKeeAgentSettings(m_sshAgentSettings);
#endif
#ifdef WITH_XC_BROWSER
@ -1085,13 +1026,8 @@ bool EditEntryWidget::commitEntry()
m_entry->endUpdate();
}
#ifdef WITH_XC_SSHAGENT
if (m_sshAgentEnabled) {
updateSSHAgent();
}
#endif
m_historyModel->setEntries(m_entry->historyItems());
m_advancedUi->attachmentsWidget->setEntryAttachments(m_entry->attachments());
showMessage(tr("Entry updated successfully."), MessageWidget::Positive);
setModified(false);
@ -1152,6 +1088,12 @@ void EditEntryWidget::updateEntryData(Entry* entry) const
}
entry->autoTypeAssociations()->copyDataFrom(m_autoTypeAssoc);
#ifdef WITH_XC_SSHAGENT
if (m_sshAgentEnabled) {
m_sshAgentSettings.toEntry(entry);
}
#endif
}
void EditEntryWidget::cancel()

View File

@ -112,6 +112,7 @@ private slots:
void toggleHideNotes(bool visible);
void pickColor();
#ifdef WITH_XC_SSHAGENT
void toKeeAgentSettings(KeeAgentSettings& settings) const;
void updateSSHAgent();
void updateSSHAgentAttachment();
void updateSSHAgentAttachments();
@ -153,7 +154,6 @@ private:
void updateEntryData(Entry* entry) const;
#ifdef WITH_XC_SSHAGENT
bool getOpenSSHKey(OpenSSHKey& key, bool decrypt = false);
void saveSSHAgentConfig();
#endif
void displayAttribute(QModelIndex index, bool showProtected);

View File

@ -19,20 +19,12 @@
#include "KeeAgentSettings.h"
KeeAgentSettings::KeeAgentSettings()
: m_allowUseOfSshKey(false)
, m_addAtDatabaseOpen(false)
, m_removeAtDatabaseClose(false)
, m_useConfirmConstraintWhenAdding(false)
, m_useLifetimeConstraintWhenAdding(false)
, m_lifetimeConstraintDuration(600)
: m_lifetimeConstraintDuration(600)
, m_selectedType(QString("file"))
, m_attachmentName(QString())
, m_saveAttachmentToTempFile(false)
, m_fileName(QString())
{
}
bool KeeAgentSettings::operator==(KeeAgentSettings& other)
bool KeeAgentSettings::operator==(const KeeAgentSettings& other) const
{
// clang-format off
return (m_allowUseOfSshKey == other.m_allowUseOfSshKey && m_addAtDatabaseOpen == other.m_addAtDatabaseOpen
@ -47,17 +39,32 @@ bool KeeAgentSettings::operator==(KeeAgentSettings& other)
// clang-format on
}
bool KeeAgentSettings::operator!=(KeeAgentSettings& other)
bool KeeAgentSettings::operator!=(const KeeAgentSettings& other) const
{
return !(*this == other);
}
bool KeeAgentSettings::isDefault()
/**
* Test if this instance is at default settings.
*
* @return true if is at default settings
*/
bool KeeAgentSettings::isDefault() const
{
KeeAgentSettings defaultSettings;
return (*this == defaultSettings);
}
/**
* Get last error as a QString.
*
* @return translated error message
*/
const QString KeeAgentSettings::errorString() const
{
return m_error;
}
bool KeeAgentSettings::allowUseOfSshKey() const
{
return m_allowUseOfSshKey;
@ -174,16 +181,26 @@ int KeeAgentSettings::readInt(QXmlStreamReader& reader)
return ret;
}
/**
* Read settings from an XML document.
*
* Sets error string on error.
*
* @param ba XML document
* @return success
*/
bool KeeAgentSettings::fromXml(const QByteArray& ba)
{
QXmlStreamReader reader;
reader.addData(ba);
if (reader.error() || !reader.readNextStartElement()) {
m_error = reader.errorString();
return false;
}
if (reader.qualifiedName() != "EntrySettings") {
m_error = QCoreApplication::translate("KeeAgentSettings", "Invalid KeeAgent settings file structure.");
return false;
}
@ -230,7 +247,12 @@ bool KeeAgentSettings::fromXml(const QByteArray& ba)
return true;
}
QByteArray KeeAgentSettings::toXml()
/**
* Write settings to an XML document.
*
* @return XML document
*/
QByteArray KeeAgentSettings::toXml() const
{
QByteArray ba;
QXmlStreamWriter writer(&ba);
@ -276,3 +298,115 @@ QByteArray KeeAgentSettings::toXml()
return ba;
}
/**
* Read settings from an entry as an XML attachment.
*
* Sets error string on error.
*
* @param entry Entry to read the attachment from
* @return true if XML document was loaded
*/
bool KeeAgentSettings::fromEntry(const Entry* entry)
{
return fromXml(entry->attachments()->value("KeeAgent.settings"));
}
/**
* Write settings to an entry as an XML attachment.
*
* @param entry Entry to create the attachment to
*/
void KeeAgentSettings::toEntry(Entry* entry) const
{
if (isDefault()) {
if (entry->attachments()->hasKey("KeeAgent.settings")) {
entry->attachments()->remove("KeeAgent.settings");
}
} else {
entry->attachments()->set("KeeAgent.settings", toXml());
}
}
/**
* Test if a SSH key is currently set to be used
*
* @return true if key is configured
*/
bool KeeAgentSettings::keyConfigured() const
{
if (m_selectedType == "attachment") {
return !m_attachmentName.isEmpty();
} else {
return !m_fileName.isEmpty();
}
}
/**
* Read a SSH key based on settings from entry to key.
*
* Sets error string on error.
*
* @param entry input entry to read attachment and decryption key
* @param key output key object
* @param decrypt avoid private key decryption if possible (old RSA keys are always decrypted)
* @return true if key was properly opened
*/
bool KeeAgentSettings::toOpenSSHKey(const Entry* entry, OpenSSHKey& key, bool decrypt)
{
QString fileName;
QByteArray privateKeyData;
if (m_selectedType == "attachment") {
fileName = m_attachmentName;
privateKeyData = entry->attachments()->value(fileName);
} else {
QFile localFile(m_fileName);
QFileInfo localFileInfo(localFile);
fileName = localFileInfo.fileName();
if (localFile.fileName().isEmpty()) {
m_error = QCoreApplication::translate("KeeAgentSettings", "Private key is empty");
return false;
}
if (localFile.size() > 1024 * 1024) {
m_error = QCoreApplication::translate("KeeAgentSettings", "File too large to be a private key");
return false;
}
if (!localFile.open(QIODevice::ReadOnly)) {
m_error = QCoreApplication::translate("KeeAgentSettings", "Failed to open private key");
return false;
}
privateKeyData = localFile.readAll();
}
if (privateKeyData.isEmpty()) {
m_error = QCoreApplication::translate("KeeAgentSettings", "Private key is empty");
return false;
}
if (!key.parsePKCS1PEM(privateKeyData)) {
m_error = key.errorString();
return false;
}
if (key.encrypted() && (decrypt || key.publicParts().isEmpty())) {
if (!key.openKey(entry->password())) {
m_error = key.errorString();
return false;
}
}
if (key.comment().isEmpty()) {
key.setComment(entry->username());
}
if (key.comment().isEmpty()) {
key.setComment(fileName);
}
return true;
}

View File

@ -19,6 +19,8 @@
#ifndef KEEAGENTSETTINGS_H
#define KEEAGENTSETTINGS_H
#include "core/Entry.h"
#include "crypto/ssh/OpenSSHKey.h"
#include <QXmlStreamReader>
#include <QtCore>
@ -27,12 +29,19 @@ class KeeAgentSettings
public:
KeeAgentSettings();
bool operator==(KeeAgentSettings& other);
bool operator!=(KeeAgentSettings& other);
bool isDefault();
bool operator==(const KeeAgentSettings& other) const;
bool operator!=(const KeeAgentSettings& other) const;
bool isDefault() const;
bool fromXml(const QByteArray& ba);
QByteArray toXml();
QByteArray toXml() const;
bool fromEntry(const Entry* entry);
void toEntry(Entry* entry) const;
bool keyConfigured() const;
bool toOpenSSHKey(const Entry* entry, OpenSSHKey& key, bool decrypt);
const QString errorString() const;
bool allowUseOfSshKey() const;
bool addAtDatabaseOpen() const;
@ -74,6 +83,7 @@ private:
QString m_attachmentName;
bool m_saveAttachmentToTempFile;
QString m_fileName;
QString m_error;
};
#endif // KEEAGENTSETTINGS_H

View File

@ -308,73 +308,31 @@ void SSHAgent::databaseModeChanged()
}
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()) {
if (!settings.fromEntry(e)) {
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()) {
if (!settings.allowUseOfSshKey() || !settings.addAtDatabaseOpen()) {
continue;
}
OpenSSHKey key;
if (!key.parsePKCS1PEM(keyData)) {
if (!settings.toOpenSSHKey(e, key, true)) {
continue;
}
if (!key.openKey(e->password())) {
continue;
}
if (key.comment().isEmpty()) {
key.setComment(e->username());
}
if (key.comment().isEmpty()) {
key.setComment(fileName);
}
if (settings.addAtDatabaseOpen()) {
// Add key to agent; ignore errors if we have previously added the key
bool known_key = m_addedKeys.contains(key);
if (!addIdentity(key, settings) && !known_key) {
emit error(m_error);
}
// Add key to agent; ignore errors if we have previously added the key
bool known_key = m_addedKeys.contains(key);
if (!addIdentity(key, settings) && !known_key) {
emit error(m_error);
}
}
}