diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts
index 126deb41b0..1be7981056 100644
--- a/share/translations/keepassxc_en.ts
+++ b/share/translations/keepassxc_en.ts
@@ -2669,6 +2669,10 @@ Would you like to correct it?
+
+
+
+
EditEntryWidgetAdvanced
@@ -3098,6 +3102,14 @@ Would you like to correct it?
+
+
+
+
+
+
+
+
EditGroupWidget
@@ -4835,6 +4847,22 @@ Line %2, column %3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
KeePass1Reader
@@ -6204,6 +6232,22 @@ We recommend you use the AppImage available on our downloads page.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
OpenSSHKeyGenDialog
@@ -9253,6 +9297,14 @@ This option is deprecated, use --set-key-file instead.
+
+
+
+
+
+
+
+
SearchHelpWidget
diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp
index 6620b08077..0523026f7c 100644
--- a/src/gui/entry/EditEntryWidget.cpp
+++ b/src/gui/entry/EditEntryWidget.cpp
@@ -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();
@@ -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
@@ -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(&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());
@@ -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();
@@ -602,6 +643,7 @@ void EditEntryWidget::updateSSHAgent()
}
updateSSHAgentAttachments();
+ blockSSHAgentSignals(false);
}
void EditEntryWidget::updateSSHAgentAttachment()
@@ -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());
@@ -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);
@@ -655,6 +714,7 @@ void EditEntryWidget::updateSSHAgentKeyInfo()
OpenSSHKey key;
if (!getOpenSSHKey(key)) {
+ blockSSHAgentSignals(false);
return;
}
@@ -687,6 +747,7 @@ void EditEntryWidget::updateSSHAgentKeyInfo()
sshAgent()->setAutoRemoveOnLock(key, m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked());
}
+ blockSSHAgentSignals(false);
}
void EditEntryWidget::toKeeAgentSettings(KeeAgentSettings& settings) const
@@ -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()
@@ -721,6 +790,7 @@ 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);
@@ -728,6 +798,7 @@ void EditEntryWidget::browsePrivateKey()
m_sshAgentUi->externalFileRadioButton->setChecked(true);
updateSSHAgentKeyInfo();
}
+ blockSSHAgentSignals(false);
}
bool EditEntryWidget::getOpenSSHKey(OpenSSHKey& key, bool decrypt)
@@ -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;
@@ -870,6 +960,7 @@ void EditEntryWidget::loadEntry(Entry* entry,
const QString& parentName,
QSharedPointer database)
{
+ blockSSHAgentSignals();
m_entry = entry;
m_db = std::move(database);
m_create = create;
@@ -900,6 +991,7 @@ void EditEntryWidget::loadEntry(Entry* entry,
showApplyButton(!m_create);
setModified(false);
+ blockSSHAgentSignals(false);
}
void EditEntryWidget::setForms(Entry* entry, bool restore)
@@ -1170,7 +1262,9 @@ bool EditEntryWidget::commitEntry()
m_autoTypeAssoc->removeEmpty();
#ifdef WITH_XC_SSHAGENT
+ blockSSHAgentSignals();
toKeeAgentSettings(m_sshAgentSettings);
+ blockSSHAgentSignals(false);
#endif
// Begin entry update
@@ -1206,6 +1300,7 @@ bool EditEntryWidget::commitEntry()
void EditEntryWidget::acceptEntry()
{
if (commitEntry()) {
+ m_sshAgentUi->privateKeyTabWidget->setCurrentIndex(0);
clear();
emit editFinished(true);
}
@@ -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);
}
@@ -1347,6 +1444,7 @@ void EditEntryWidget::clear()
m_historyModel->clear();
m_iconsWidget->reset();
hideMessage();
+ blockSSHAgentSignals(false);
}
#ifdef WITH_XC_NETWORKING
diff --git a/src/gui/entry/EditEntryWidget.h b/src/gui/entry/EditEntryWidget.h
index 0507b5dfbc..e44bb74305 100644
--- a/src/gui/entry/EditEntryWidget.h
+++ b/src/gui/entry/EditEntryWidget.h
@@ -125,6 +125,8 @@ private slots:
void decryptPrivateKey();
void copyPublicKey();
void generatePrivateKey();
+ void updateSSHAgentAttachmentCertificate();
+ void browseCertificate();
#endif
#ifdef WITH_XC_BROWSER
void updateBrowserModified();
@@ -146,6 +148,7 @@ private slots:
#endif
#ifdef WITH_XC_SSHAGENT
void setupSSHAgent();
+ void blockSSHAgentSignals(const bool block = true);
#endif
void setupProperties();
void setupHistory();
@@ -170,6 +173,8 @@ private slots:
#ifdef WITH_XC_SSHAGENT
KeeAgentSettings m_sshAgentSettings;
QString m_pendingPrivateKey;
+ QPointer m_entryCertificate;
+ const QScopedPointer m_attachmentsCertificate;
#endif
const QScopedPointer m_mainUi;
const QScopedPointer m_advancedUi;
@@ -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)
};
diff --git a/src/gui/entry/EditEntryWidgetSSHAgent.ui b/src/gui/entry/EditEntryWidgetSSHAgent.ui
index 3fa48baf33..7253e73e8f 100644
--- a/src/gui/entry/EditEntryWidgetSSHAgent.ui
+++ b/src/gui/entry/EditEntryWidgetSSHAgent.ui
@@ -26,13 +26,73 @@
0
- -
-
+
-
+
+
-
+
+
+ Add to agent
+
+
+
+ -
+
+
+ Remove from agent
+
+
+
+
+
+ -
+
- Remove key from agent when database is closed/locked
+ Require user confirmation when this key is used
+ -
+
+
+ Public key
+
+
+ Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing
+
+
+
+ -
+
+
-
+
+
+
+ Monospace
+
+
+
+ n/a
+
+
+ Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
-
@@ -43,14 +103,17 @@
- -
-
+
-
+
- Add key to agent when database is opened/unlocked
+ Fingerprint
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
- -
+
-
@@ -62,15 +125,15 @@
- -
+
-
Decrypt
- -
-
+
-
+
Qt::Vertical
@@ -85,131 +148,10 @@
- -
-
-
- Fingerprint
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- Copy to clipboard
-
-
-
- -
-
-
- Public key
-
-
- Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing
-
-
-
- -
-
-
- Private key
-
-
-
-
-
-
- Attachment
-
-
- true
-
-
-
- -
-
-
- Qt::ClickFocus
-
-
- External key file
-
-
-
- -
-
-
-
-
-
- Add to agent
-
-
-
- -
-
-
- Remove from agent
-
-
-
-
-
- -
-
-
- External file
-
-
-
- -
-
-
- Browser for key file
-
-
- Browse…
-
-
-
- -
-
-
- Generate
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Select attachment file
-
-
- false
-
-
-
-
-
-
- -
-
-
- Require user confirmation when this key is used
-
-
-
- -
-
+
-
+
-
-
+
Monospace
@@ -224,7 +166,7 @@
-
-
+
Qt::Horizontal
@@ -238,7 +180,204 @@
- -
+
-
+
+
+ Add key to agent when database is opened/unlocked
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 10
+
+
+
+
+ -
+
+
+ Remove key from agent when database is closed/locked
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+
+ Private key
+
+
+
-
+
+
+ Use certificate
+
+
+
+ -
+
+
+ External file
+
+
+
+ -
+
+
+ Browser for key file
+
+
+ Browse…
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Select attachment file
+
+
+ false
+
+
+
+ -
+
+
+ Attachment
+
+
+ true
+
+
+
+ -
+
+
+ Qt::ClickFocus
+
+
+ External key file
+
+
+
+ -
+
+
+ Generate
+
+
+
+
+
+
+
+ Certificate
+
+
+ -
+
+
+ Attachment
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Select attachment file
+
+
+ false
+
+
+
+ -
+
+
+ Qt::ClickFocus
+
+
+ External key file
+
+
+
+ -
+
+
+ External file
+
+
+
+ -
+
+
+ Browser for key file
+
+
+ Browse…
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+
+ -
+
+
+ Copy to clipboard
+
+
+
+ -
-
@@ -275,54 +414,6 @@
- -
-
-
-
-
-
-
- Monospace
-
-
-
- n/a
-
-
- Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
- QSizePolicy::Fixed
-
-
-
- 20
- 10
-
-
-
-
diff --git a/src/sshagent/KeeAgentSettings.cpp b/src/sshagent/KeeAgentSettings.cpp
index 272fb7edf7..c2f363df23 100644
--- a/src/sshagent/KeeAgentSettings.cpp
+++ b/src/sshagent/KeeAgentSettings.cpp
@@ -46,7 +46,12 @@ bool KeeAgentSettings::operator==(const KeeAgentSettings& other) const
&& m_selectedType == other.m_selectedType
&& m_attachmentName == other.m_attachmentName
&& m_saveAttachmentToTempFile == other.m_saveAttachmentToTempFile
- && m_fileName == other.m_fileName);
+ && m_fileName == other.m_fileName
+ && m_selectedCertificateType == other.m_selectedCertificateType
+ && m_attachmentNameCertificate == other.m_attachmentNameCertificate
+ && m_saveAttachmentCertificateToTempFile == other.m_saveAttachmentCertificateToTempFile
+ && m_fileNameCertificate == other.m_fileNameCertificate
+ && m_useCertificate == other.m_useCertificate);
// clang-format on
}
@@ -83,6 +88,11 @@ void KeeAgentSettings::reset()
m_saveAttachmentToTempFile = false;
m_fileName.clear();
m_error.clear();
+ m_selectedCertificateType = QStringLiteral("file");
+ m_attachmentNameCertificate.clear();
+ m_saveAttachmentCertificateToTempFile = false;
+ m_fileNameCertificate.clear();
+ m_useCertificate = false;
}
/**
@@ -200,6 +210,61 @@ void KeeAgentSettings::setFileName(const QString& fileName)
m_fileName = fileName;
}
+const QString KeeAgentSettings::fileNameCertificateEnvSubst(QProcessEnvironment environment) const
+{
+ return Tools::envSubstitute(m_fileNameCertificate, environment);
+}
+
+bool KeeAgentSettings::useCertificate() const
+{
+ return m_useCertificate;
+}
+
+void KeeAgentSettings::setUseCertificate(bool useCertificate)
+{
+ m_useCertificate = useCertificate;
+}
+
+const QString KeeAgentSettings::selectedCertificateType() const
+{
+ return m_selectedCertificateType;
+}
+
+const QString KeeAgentSettings::attachmentNameCertificate() const
+{
+ return m_attachmentNameCertificate;
+}
+
+bool KeeAgentSettings::saveAttachmentCertificateToTempFile() const
+{
+ return m_saveAttachmentCertificateToTempFile;
+}
+
+const QString KeeAgentSettings::fileNameCertificate() const
+{
+ return m_fileNameCertificate;
+}
+
+void KeeAgentSettings::setSelectedCertificateType(const QString& selectedCertificateType)
+{
+ m_selectedCertificateType = selectedCertificateType;
+}
+
+void KeeAgentSettings::setAttachmentCertificateName(const QString& attachmentCertificateName)
+{
+ m_attachmentNameCertificate = attachmentCertificateName;
+}
+
+void KeeAgentSettings::setSaveAttachmentCertificateToTempFile(bool saveAttachmentCertificateToTempFile)
+{
+ m_saveAttachmentCertificateToTempFile = saveAttachmentCertificateToTempFile;
+}
+
+void KeeAgentSettings::setFileNameCertificate(const QString& fileNameCertificate)
+{
+ m_fileNameCertificate = fileNameCertificate;
+}
+
bool KeeAgentSettings::readBool(QXmlStreamReader& reader)
{
reader.readNext();
@@ -273,6 +338,29 @@ bool KeeAgentSettings::fromXml(const QByteArray& ba)
reader.skipCurrentElement();
}
}
+ } else if (reader.name() == "UseCertificate") {
+ m_useCertificate = readBool(reader);
+ } else if (reader.name() == "LocationCertificate") {
+ while (!reader.error() && reader.readNextStartElement()) {
+ if (reader.name() == "SelectedCertificateType") {
+ reader.readNext();
+ m_selectedCertificateType = reader.text().toString();
+ reader.readNext();
+ } else if (reader.name() == "AttachmentCertificateName") {
+ reader.readNext();
+ m_attachmentNameCertificate = reader.text().toString();
+ reader.readNext();
+ } else if (reader.name() == "SaveAttachmentCertificateToTempFile") {
+ m_saveAttachmentCertificateToTempFile = readBool(reader);
+ } else if (reader.name() == "FileNameCertificate") {
+ reader.readNext();
+ m_fileNameCertificate = reader.text().toString();
+ reader.readNext();
+ } else {
+ qWarning() << "Skipping location certificate element" << reader.name();
+ reader.skipCurrentElement();
+ }
+ }
} else {
qWarning() << "Skipping element" << reader.name();
reader.skipCurrentElement();
@@ -328,6 +416,27 @@ QByteArray KeeAgentSettings::toXml() const
}
writer.writeEndElement(); // Location
+
+ writer.writeTextElement("UseCertificate", m_useCertificate ? "true" : "false");
+ writer.writeStartElement("LocationCertificate");
+
+ writer.writeTextElement("SelectedCertificateType", m_selectedCertificateType);
+
+ if (!m_attachmentNameCertificate.isEmpty()) {
+ writer.writeTextElement("AttachmentCertificateName", m_attachmentNameCertificate);
+ } else {
+ writer.writeEmptyElement("AttachmentCertificateName");
+ }
+
+ writer.writeTextElement("SaveAttachmentCertificateToTempFile", m_saveAttachmentCertificateToTempFile ? "true" : "false");
+
+ if (!m_fileNameCertificate.isEmpty()) {
+ writer.writeTextElement("FileNameCertificate", m_fileNameCertificate);
+ } else {
+ writer.writeEmptyElement("FileNameCertificate");
+ }
+
+ writer.writeEndElement(); // LocationCertificate
writer.writeEndElement(); // EntrySettings
writer.writeEndDocument();
@@ -497,5 +606,61 @@ bool KeeAgentSettings::toOpenSSHKey(const QString& username,
key.setComment(fileName);
}
+ if (m_useCertificate) {
+ QString fileCertificateName;
+ QByteArray certificateData;
+
+ if (m_selectedCertificateType == "attachment") {
+ if (!attachments) {
+ m_error = QCoreApplication::translate("KeeAgentSettings",
+ "Certificate is an attachment but no attachments provided.");
+ return false;
+ }
+
+ fileCertificateName = m_attachmentNameCertificate;
+ certificateData = attachments->value(fileCertificateName);
+ } else {
+ QString fileNameCertificateSubst = fileNameCertificateEnvSubst();
+ QFileInfo localFileCertificateInfo(fileNameCertificateSubst);
+
+ // resolve relative certificate path from database location
+ if (localFileCertificateInfo.isRelative()) {
+ QFileInfo databaseFileCertificateInfo(databasePath);
+ localFileCertificateInfo = QFileInfo(databaseFileCertificateInfo.absolutePath() + QDir::separator() + fileNameCertificateSubst);
+ }
+
+ fileCertificateName = localFileCertificateInfo.fileName();
+
+ QFile localCertificateFile(localFileCertificateInfo.absoluteFilePath());
+
+ if (localCertificateFile.fileName().isEmpty()) {
+ m_error = QCoreApplication::translate("KeeAgentSettings", "Certificate is empty");
+ return false;
+ }
+
+ if (localCertificateFile.size() > 1024 * 1024) {
+ m_error = QCoreApplication::translate("KeeAgentSettings", "File too large to be a certificate");
+ return false;
+ }
+
+ if (!localCertificateFile.open(QIODevice::ReadOnly)) {
+ m_error = QCoreApplication::translate("KeeAgentSettings", "Failed to open certificate");
+ return false;
+ }
+
+ certificateData = localCertificateFile.readAll();
+ }
+
+ if (certificateData.isEmpty()) {
+ m_error = QCoreApplication::translate("KeeAgentSettings", "Certificate is empty");
+ return false;
+ }
+
+ if (!key.parseCertificate(certificateData)) {
+ m_error = key.errorString();
+ return false;
+ }
+ }
+
return true;
}
diff --git a/src/sshagent/KeeAgentSettings.h b/src/sshagent/KeeAgentSettings.h
index ffc14044ee..113d4f982a 100644
--- a/src/sshagent/KeeAgentSettings.h
+++ b/src/sshagent/KeeAgentSettings.h
@@ -77,6 +77,19 @@ class KeeAgentSettings
void setSaveAttachmentToTempFile(bool);
void setFileName(const QString& fileName);
+ // Certificate
+ const QString fileNameCertificateEnvSubst(QProcessEnvironment environment = QProcessEnvironment::systemEnvironment()) const;
+ bool useCertificate() const;
+ void setUseCertificate(bool UseCertificate);
+ const QString selectedCertificateType() const;
+ const QString attachmentNameCertificate() const;
+ bool saveAttachmentCertificateToTempFile() const;
+ const QString fileNameCertificate() const;
+ void setSelectedCertificateType(const QString& certificateType);
+ void setAttachmentCertificateName(const QString& attachmentCertificateName);
+ void setSaveAttachmentCertificateToTempFile(bool);
+ void setFileNameCertificate(const QString& fileNameCertificate);
+
private:
bool readBool(QXmlStreamReader& reader);
int readInt(QXmlStreamReader& reader);
@@ -94,6 +107,13 @@ class KeeAgentSettings
bool m_saveAttachmentToTempFile;
QString m_fileName;
QString m_error;
+
+ // Certificate
+ bool m_useCertificate;
+ QString m_selectedCertificateType;
+ QString m_attachmentNameCertificate;
+ bool m_saveAttachmentCertificateToTempFile;
+ QString m_fileNameCertificate;
};
#endif // KEEAGENTSETTINGS_H
diff --git a/src/sshagent/OpenSSHKey.cpp b/src/sshagent/OpenSSHKey.cpp
index cdcc257013..8db2d5852f 100644
--- a/src/sshagent/OpenSSHKey.cpp
+++ b/src/sshagent/OpenSSHKey.cpp
@@ -46,6 +46,8 @@ OpenSSHKey::OpenSSHKey(QObject* parent)
, m_rawPrivateData(QByteArray())
, m_comment(QString())
, m_error(QString())
+ , m_certificateType(QString())
+ , m_rawCertificateData(QByteArray())
{
}
@@ -62,6 +64,8 @@ OpenSSHKey::OpenSSHKey(const OpenSSHKey& other)
, m_rawPrivateData(other.m_rawPrivateData)
, m_comment(other.m_comment)
, m_error(other.m_error)
+ , m_certificateType(other.m_certificateType)
+ , m_rawCertificateData(other.m_rawCertificateData)
{
}
@@ -81,6 +85,11 @@ const QString OpenSSHKey::type() const
return m_type;
}
+const QString OpenSSHKey::certificateType() const
+{
+ return m_certificateType;
+}
+
const QString OpenSSHKey::fingerprint(QCryptographicHash::Algorithm algo) const
{
if (m_rawPublicData.isEmpty()) {
@@ -656,6 +665,82 @@ bool OpenSSHKey::writePrivate(BinaryStream& stream)
return true;
}
+bool OpenSSHKey::parseCertificate(QByteArray& data)
+{
+ QString stringData = QString::fromLatin1(data);
+ QStringList elements = stringData.split(QRegularExpression("\\s+"), QString::SkipEmptyParts);
+
+ if (elements.length() >= 2 && elements.length() <= 3) {
+ m_error = tr("Invalid certificate file, expecting an OpenSSH certificate");
+ return false;
+ }
+
+ QStringList certificateTypeList = {
+ "ssh-ed25519-cert-v01@openssh.com",
+ "ssh-rsa-cert-v01@openssh.com",
+ "ssh-dss-cert-v01@openssh.com",
+ "sk-ssh-ed25519-cert-v01@openssh.com",
+ "sk-ssh-rsa-cert-v01@openssh.com",
+ "sk-ssh-dss-cert-v01@openssh.com"
+ "rsa-sha2-256-cert-v01@openssh.com",
+ "sk-rsa-sha2-256-cert-v01@openssh.com",
+ "rsa-sha2-512-cert-v01@openssh.com",
+ "sk-rsa-sha2-512-cert-v01@openssh.com",
+ "ecdsa-sha2-nistp256-cert-v01@openssh.com",
+ "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com",
+ "ecdsa-sha2-nistp384-cert-v01@openssh.com",
+ "sk-ecdsa-sha2-nistp384-cert-v01@openssh.com",
+ "ecdsa-sha2-nistp521-cert-v01@openssh.com",
+ "sk-ecdsa-sha2-nistp521-cert-v01@openssh.com",
+ };
+
+ if(!certificateTypeList.contains(elements.first())) {
+ m_error = tr("Unsupported certificate file");
+ return false;
+ }
+
+ m_certificateType = elements.first();
+ m_rawCertificateData = QByteArray::fromBase64(elements[1].toLatin1());
+ // if (elements.length() == 3) {m_certificateComment = elements.last();}
+
+ return true;
+}
+
+bool OpenSSHKey::writeCertificate(BinaryStream& stream, const bool addCertificate)
+{
+ if (m_rawCertificateData.isEmpty()) {
+ m_error = tr("Can't write certificate as it is empty");
+ return false;
+ }
+
+ if (!addCertificate) {
+ if (!stream.writeString(m_rawCertificateData)) {
+ m_error = tr("Unexpected EOF when writing certificate");
+ return false;
+ }
+ return true;
+ }
+
+ stream.writeString(m_certificateType);
+
+ if (!stream.writeString(m_rawCertificateData)) {
+ m_error = tr("Unexpected EOF when writing certificate");
+ return false;
+ }
+
+ if (!stream.write(m_rawPrivateData)) {
+ m_error = tr("Unexpected EOF when writing certificate");
+ return false;
+ }
+
+ if (!stream.writeString(m_comment)) {
+ m_error = tr("Unexpected EOF when writing certificate");
+ return false;
+ }
+
+ return true;
+}
+
uint qHash(const OpenSSHKey& key)
{
return qHash(key.fingerprint());
diff --git a/src/sshagent/OpenSSHKey.h b/src/sshagent/OpenSSHKey.h
index c2c8319398..f4ba1c21d4 100644
--- a/src/sshagent/OpenSSHKey.h
+++ b/src/sshagent/OpenSSHKey.h
@@ -62,6 +62,10 @@ class OpenSSHKey : public QObject
static const QString TYPE_OPENSSH_PRIVATE;
static const QString OPENSSH_CIPHER_SUFFIX;
+ bool parseCertificate(QByteArray& data);
+ bool writeCertificate(BinaryStream& stream, const bool addCertificate = true);
+ const QString certificateType() const;
+
private:
enum KeyPart
{
@@ -85,6 +89,8 @@ class OpenSSHKey : public QObject
QByteArray m_rawPrivateData;
QString m_comment;
QString m_error;
+ QString m_certificateType;
+ QByteArray m_rawCertificateData;
};
uint qHash(const OpenSSHKey& key);
diff --git a/src/sshagent/SSHAgent.cpp b/src/sshagent/SSHAgent.cpp
index 5c423cc755..3469117bcc 100644
--- a/src/sshagent/SSHAgent.cpp
+++ b/src/sshagent/SSHAgent.cpp
@@ -330,6 +330,60 @@ bool SSHAgent::addIdentity(OpenSSHKey& key, const KeeAgentSettings& settings, co
return false;
}
+ if (settings.useCertificate()) {
+ QByteArray requestCertificateData;
+ BinaryStream requestCertificate(&requestCertificateData);
+ bool isSecurityCertificate = key.certificateType().startsWith("sk-");
+
+ requestCertificate.write(
+ (settings.useLifetimeConstraintWhenAdding() || settings.useConfirmConstraintWhenAdding() || isSecurityCertificate)
+ ? SSH_AGENTC_ADD_ID_CONSTRAINED
+ : SSH_AGENTC_ADD_IDENTITY);
+
+ key.writeCertificate(requestCertificate);
+
+ if (settings.useLifetimeConstraintWhenAdding()) {
+ requestCertificate.write(SSH_AGENT_CONSTRAIN_LIFETIME);
+ requestCertificate.write(static_cast(settings.lifetimeConstraintDuration()));
+ }
+
+ if (settings.useConfirmConstraintWhenAdding()) {
+ requestCertificate.write(SSH_AGENT_CONSTRAIN_CONFIRM);
+ }
+
+ // To be verified if useful with certificates
+ if (isSecurityCertificate) {
+ requestCertificate.write(SSH_AGENT_CONSTRAIN_EXTENSION);
+ requestCertificate.writeString(QString("sk-provider@openssh.com"));
+ requestCertificate.writeString(securityKeyProvider());
+ }
+
+ QByteArray responseCertificateData;
+ if (!sendMessage(requestCertificateData, responseCertificateData)) {
+ return false;
+ }
+
+ if (responseCertificateData.length() < 1 || static_cast(responseCertificateData[0]) != SSH_AGENT_SUCCESS) {
+ m_error =
+ tr("Agent refused this identity certificate. Possible reasons include:") + "\n" + tr("Invalid or empty certificate."); "\n" + tr("The key has already been added.");
+
+ if (settings.useLifetimeConstraintWhenAdding()) {
+ m_error += "\n" + tr("Restricted lifetime is not supported by the agent (check options).");
+ }
+
+ if (settings.useConfirmConstraintWhenAdding()) {
+ m_error += "\n" + tr("A confirmation request is not supported by the agent (check options).");
+ }
+
+ if (isSecurityKey) {
+ m_error +=
+ "\n" + tr("Security keys are not supported by the agent or the security key provider is unavailable.");
+ }
+
+ return false;
+ }
+ }
+
OpenSSHKey keyCopy = key;
keyCopy.clearPrivate();
m_addedKeys[keyCopy] = qMakePair(databaseUuid, settings.removeAtDatabaseClose());
@@ -360,7 +414,22 @@ bool SSHAgent::removeIdentity(OpenSSHKey& key)
request.writeString(keyData);
QByteArray responseData;
- return sendMessage(requestData, responseData);
+
+ // Try to remove certificate
+ QByteArray requestCertificateData;
+ BinaryStream requestCertificate(&requestCertificateData);
+
+ QByteArray certificateData;
+ BinaryStream certificateStream(&certificateData);
+ key.writeCertificate(certificateStream, false);
+
+ requestCertificate.write(SSH_AGENTC_REMOVE_IDENTITY);
+ requestCertificate.write(certificateData);
+
+ QByteArray responseCertificateData;
+
+ return (sendMessage(requestData, responseData) &&
+ sendMessage(requestCertificateData, responseCertificateData));
}
/**