mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-28 16:59:44 -05:00
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:
parent
c97ee5395b
commit
e24a858f39
@ -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()
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user