Skip to content

Commit

Permalink
SSH Agent: Add support for certificates (keepassxreboot#5486)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexpFr committed May 5, 2024
1 parent 92b30ae commit d2ad304
Show file tree
Hide file tree
Showing 9 changed files with 778 additions and 186 deletions.
52 changes: 52 additions & 0 deletions share/translations/keepassxc_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2669,6 +2669,10 @@ Would you like to correct it?</source>
<numerusform></numerusform>
</translation>
</message>
<message>
<source>Select certificate</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>EditEntryWidgetAdvanced</name>
Expand Down Expand Up @@ -3098,6 +3102,14 @@ Would you like to correct it?</source>
<source> seconds</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use certificate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Certificate</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>EditGroupWidget</name>
Expand Down Expand Up @@ -4835,6 +4847,22 @@ Line %2, column %3</source>
<source>Failed to open private key</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Certificate is an attachment but no attachments provided.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Certificate is empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>File too large to be a certificate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to open certificate</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>KeePass1Reader</name>
Expand Down Expand Up @@ -6204,6 +6232,22 @@ We recommend you use the AppImage available on our downloads page.</source>
<source>Unexpected EOF when writing private key</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid certificate file, expecting an OpenSSH certificate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unsupported certificate file</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Can&apos;t write certificate as it is empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unexpected EOF when writing certificate</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>OpenSSHKeyGenDialog</name>
Expand Down Expand Up @@ -9253,6 +9297,14 @@ This option is deprecated, use --set-key-file instead.</source>
<source>No agent running, cannot list identities.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Agent refused this identity certificate. Possible reasons include:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid or empty certificate.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SearchHelpWidget</name>
Expand Down
98 changes: 98 additions & 0 deletions src/gui/entry/EditEntryWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
, m_autoTypeWindowSequenceGroup(new QButtonGroup(this))
, m_usernameCompleter(new QCompleter(this))
, m_usernameCompleterModel(new QStringListModel(this))
, m_blockSSHAgentSignals(new bool)
{
setupMain();
setupAdvanced();
Expand Down Expand Up @@ -492,6 +493,12 @@ void EditEntryWidget::setupEntryUpdate()
connect(m_sshAgentUi->requireUserConfirmationCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_sshAgentUi->lifetimeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_sshAgentUi->lifetimeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setModified()));
connect(m_sshAgentUi->attachmentCertificateRadioButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
connect(m_sshAgentUi->externalCertificateFileRadioButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
connect(m_sshAgentUi->attachmentCertificateComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setModified()));
connect(m_sshAgentUi->attachmentCertificateComboBox, SIGNAL(editTextChanged(QString)), this, SLOT(setModified()));
connect(m_sshAgentUi->externalCertificateFileEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
connect(m_sshAgentUi->addCertificateToAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
}
#endif

Expand Down Expand Up @@ -568,16 +575,46 @@ void EditEntryWidget::setupSSHAgent()
connect(m_sshAgentUi->decryptButton, &QPushButton::clicked, this, &EditEntryWidget::decryptPrivateKey);
connect(m_sshAgentUi->copyToClipboardButton, &QPushButton::clicked, this, &EditEntryWidget::copyPublicKey);
connect(m_sshAgentUi->generateButton, &QPushButton::clicked, this, &EditEntryWidget::generatePrivateKey);
connect(m_sshAgentUi->attachmentCertificateRadioButton, &QRadioButton::clicked,
this, &EditEntryWidget::updateSSHAgentKeyInfo);
connect(m_sshAgentUi->attachmentCertificateComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, &EditEntryWidget::updateSSHAgentAttachmentCertificate);
connect(m_sshAgentUi->externalCertificateFileRadioButton, &QRadioButton::clicked,
this, &EditEntryWidget::updateSSHAgentKeyInfo);
connect(m_sshAgentUi->externalCertificateFileEdit, &QLineEdit::textChanged,
this, &EditEntryWidget::updateSSHAgentKeyInfo);
connect(m_sshAgentUi->browseCertificateButton, &QPushButton::clicked, this, &EditEntryWidget::browseCertificate);

connect(m_attachments.data(), &EntryAttachments::modified,
this, &EditEntryWidget::updateSSHAgentAttachments);
// clang-format on

blockSSHAgentSignals(false);

addPage(tr("SSH Agent"), icons()->icon("utilities-terminal"), m_sshAgentWidget);
}

void EditEntryWidget::blockSSHAgentSignals(const bool block)
{
if (block == m_blockSSHAgentSignals) {
return;
}
m_blockSSHAgentSignals = block;

m_sshAgentUi->attachmentRadioButton->blockSignals(block);
m_sshAgentUi->attachmentComboBox->blockSignals(block);
m_sshAgentUi->externalFileRadioButton->blockSignals(block);
m_sshAgentUi->externalFileEdit->blockSignals(block);
m_attachments.data()->blockSignals(block);
m_sshAgentUi->attachmentCertificateRadioButton->blockSignals(block);
m_sshAgentUi->attachmentCertificateComboBox->blockSignals(block);
m_sshAgentUi->externalCertificateFileRadioButton->blockSignals(block);
m_sshAgentUi->externalCertificateFileEdit->blockSignals(block);
}

void EditEntryWidget::setSSHAgentSettings()
{
blockSSHAgentSignals();
m_sshAgentUi->addKeyToAgentCheckBox->setChecked(m_sshAgentSettings.addAtDatabaseOpen());
m_sshAgentUi->removeKeyFromAgentCheckBox->setChecked(m_sshAgentSettings.removeAtDatabaseClose());
m_sshAgentUi->requireUserConfirmationCheckBox->setChecked(m_sshAgentSettings.useConfirmConstraintWhenAdding());
Expand All @@ -587,10 +624,14 @@ void EditEntryWidget::setSSHAgentSettings()
m_sshAgentUi->addToAgentButton->setEnabled(false);
m_sshAgentUi->removeFromAgentButton->setEnabled(false);
m_sshAgentUi->copyToClipboardButton->setEnabled(false);
m_sshAgentUi->addCertificateToAgentCheckBox->setChecked(m_sshAgentSettings.useCertificate()); // AlexpFr redondant ?
m_sshAgentUi->attachmentCertificateComboBox->clear(); // AlexpFr: why ?
blockSSHAgentSignals(false);
}

void EditEntryWidget::updateSSHAgent()
{
blockSSHAgentSignals();
m_sshAgentSettings.reset();
m_sshAgentSettings.fromEntry(m_entry);
setSSHAgentSettings();
Expand All @@ -602,6 +643,7 @@ void EditEntryWidget::updateSSHAgent()
}

updateSSHAgentAttachments();
blockSSHAgentSignals(false);
}

void EditEntryWidget::updateSSHAgentAttachment()
Expand All @@ -618,16 +660,21 @@ void EditEntryWidget::updateSSHAgentAttachments()
m_sshAgentSettings.reset();
setSSHAgentSettings();
}
blockSSHAgentSignals();

m_sshAgentUi->attachmentComboBox->clear();
m_sshAgentUi->attachmentComboBox->addItem("");

m_sshAgentUi->attachmentCertificateComboBox->clear();
m_sshAgentUi->attachmentCertificateComboBox->addItem("");

for (const QString& fileName : m_attachments->keys()) {
if (fileName == "KeeAgent.settings") {
continue;
}

m_sshAgentUi->attachmentComboBox->addItem(fileName);
m_sshAgentUi->attachmentCertificateComboBox->addItem(fileName);
}

m_sshAgentUi->attachmentComboBox->setCurrentText(m_sshAgentSettings.attachmentName());
Expand All @@ -639,11 +686,23 @@ void EditEntryWidget::updateSSHAgentAttachments()
m_sshAgentUi->externalFileRadioButton->setChecked(true);
}

m_sshAgentUi->attachmentCertificateComboBox->setCurrentText(m_sshAgentSettings.attachmentNameCertificate());
m_sshAgentUi->externalCertificateFileEdit->setText(m_sshAgentSettings.fileNameCertificate());

if (m_sshAgentSettings.selectedCertificateType() == "attachment") {
m_sshAgentUi->attachmentCertificateRadioButton->setChecked(true);
} else {
m_sshAgentUi->externalCertificateFileRadioButton->setChecked(true);
}

blockSSHAgentSignals(false);

updateSSHAgentKeyInfo();
}

void EditEntryWidget::updateSSHAgentKeyInfo()
{
blockSSHAgentSignals();
m_sshAgentUi->addToAgentButton->setEnabled(false);
m_sshAgentUi->removeFromAgentButton->setEnabled(false);
m_sshAgentUi->copyToClipboardButton->setEnabled(false);
Expand All @@ -655,6 +714,7 @@ void EditEntryWidget::updateSSHAgentKeyInfo()
OpenSSHKey key;

if (!getOpenSSHKey(key)) {
blockSSHAgentSignals(false);
return;
}

Expand Down Expand Up @@ -687,6 +747,7 @@ void EditEntryWidget::updateSSHAgentKeyInfo()

sshAgent()->setAutoRemoveOnLock(key, m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked());
}
blockSSHAgentSignals(false);
}

void EditEntryWidget::toKeeAgentSettings(KeeAgentSettings& settings) const
Expand All @@ -710,6 +771,14 @@ void EditEntryWidget::toKeeAgentSettings(KeeAgentSettings& settings) const

// we don't use this either but we don't want it to dirty flag the config
settings.setSaveAttachmentToTempFile(m_sshAgentSettings.saveAttachmentToTempFile());

settings.setUseCertificate(m_sshAgentUi->addCertificateToAgentCheckBox->isChecked());
settings.setSelectedCertificateType(m_sshAgentUi->attachmentCertificateRadioButton->isChecked() ? "attachment" : "file");
settings.setAttachmentCertificateName(m_sshAgentUi->attachmentCertificateComboBox->currentText());
settings.setFileNameCertificate(m_sshAgentUi->externalCertificateFileEdit->text());

// we don't use this either but we don't want it to dirty flag the config
settings.setSaveAttachmentCertificateToTempFile(m_sshAgentSettings.saveAttachmentCertificateToTempFile());
}

void EditEntryWidget::updateTotp()
Expand All @@ -721,13 +790,15 @@ void EditEntryWidget::updateTotp()

void EditEntryWidget::browsePrivateKey()
{
blockSSHAgentSignals();
auto fileName = fileDialog()->getOpenFileName(this, tr("Select private key"), FileDialog::getLastDir("sshagent"));
if (!fileName.isEmpty()) {
FileDialog::saveLastDir("sshagent", fileName);
m_sshAgentUi->externalFileEdit->setText(fileName);
m_sshAgentUi->externalFileRadioButton->setChecked(true);
updateSSHAgentKeyInfo();
}
blockSSHAgentSignals(false);
}

bool EditEntryWidget::getOpenSSHKey(OpenSSHKey& key, bool decrypt)
Expand Down Expand Up @@ -772,6 +843,25 @@ void EditEntryWidget::addKeyToAgent()
}
}

void EditEntryWidget::updateSSHAgentAttachmentCertificate()
{
m_sshAgentUi->attachmentCertificateRadioButton->setChecked(true);
updateSSHAgentKeyInfo();
}

void EditEntryWidget::browseCertificate()
{
blockSSHAgentSignals();
auto fileName = fileDialog()->getOpenFileName(this, tr("Select certificate"), FileDialog::getLastDir("sshagent"));
if (!fileName.isEmpty()) {
FileDialog::saveLastDir("sshagent", fileName);
m_sshAgentUi->externalCertificateFileEdit->setText(fileName);
m_sshAgentUi->externalCertificateFileRadioButton->setChecked(true);
updateSSHAgentKeyInfo();
}
blockSSHAgentSignals(false);
}

void EditEntryWidget::removeKeyFromAgent()
{
OpenSSHKey key;
Expand Down Expand Up @@ -870,6 +960,7 @@ void EditEntryWidget::loadEntry(Entry* entry,
const QString& parentName,
QSharedPointer<Database> database)
{
blockSSHAgentSignals();
m_entry = entry;
m_db = std::move(database);
m_create = create;
Expand Down Expand Up @@ -900,6 +991,7 @@ void EditEntryWidget::loadEntry(Entry* entry,
showApplyButton(!m_create);

setModified(false);
blockSSHAgentSignals(false);
}

void EditEntryWidget::setForms(Entry* entry, bool restore)
Expand Down Expand Up @@ -1170,7 +1262,9 @@ bool EditEntryWidget::commitEntry()
m_autoTypeAssoc->removeEmpty();

#ifdef WITH_XC_SSHAGENT
blockSSHAgentSignals();
toKeeAgentSettings(m_sshAgentSettings);
blockSSHAgentSignals(false);
#endif

// Begin entry update
Expand Down Expand Up @@ -1206,6 +1300,7 @@ bool EditEntryWidget::commitEntry()
void EditEntryWidget::acceptEntry()
{
if (commitEntry()) {
m_sshAgentUi->privateKeyTabWidget->setCurrentIndex(0);
clear();
emit editFinished(true);
}
Expand Down Expand Up @@ -1322,12 +1417,14 @@ void EditEntryWidget::cancel()
}
}

m_sshAgentUi->privateKeyTabWidget->setCurrentIndex(0);
clear();
emit editFinished(accepted);
}

void EditEntryWidget::clear()
{
blockSSHAgentSignals();
if (m_entry) {
m_entry->disconnect(this);
}
Expand All @@ -1347,6 +1444,7 @@ void EditEntryWidget::clear()
m_historyModel->clear();
m_iconsWidget->reset();
hideMessage();
blockSSHAgentSignals(false);
}

#ifdef WITH_XC_NETWORKING
Expand Down
6 changes: 6 additions & 0 deletions src/gui/entry/EditEntryWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ private slots:
void decryptPrivateKey();
void copyPublicKey();
void generatePrivateKey();
void updateSSHAgentAttachmentCertificate();
void browseCertificate();
#endif
#ifdef WITH_XC_BROWSER
void updateBrowserModified();
Expand All @@ -146,6 +148,7 @@ private slots:
#endif
#ifdef WITH_XC_SSHAGENT
void setupSSHAgent();
void blockSSHAgentSignals(const bool block = true);
#endif
void setupProperties();
void setupHistory();
Expand All @@ -170,6 +173,8 @@ private slots:
#ifdef WITH_XC_SSHAGENT
KeeAgentSettings m_sshAgentSettings;
QString m_pendingPrivateKey;
QPointer<Entry> m_entryCertificate;
const QScopedPointer<EntryAttachments> m_attachmentsCertificate;
#endif
const QScopedPointer<Ui::EditEntryWidgetMain> m_mainUi;
const QScopedPointer<Ui::EditEntryWidgetAdvanced> m_advancedUi;
Expand Down Expand Up @@ -206,6 +211,7 @@ private slots:
QCompleter* const m_usernameCompleter;
QStringListModel* const m_usernameCompleterModel;
QTimer m_entryModifiedTimer;
bool m_blockSSHAgentSignals = false;

Q_DISABLE_COPY(EditEntryWidget)
};
Expand Down
Loading

0 comments on commit d2ad304

Please sign in to comment.