SSH Agent: Add support for generating SSH keys

Supported key types are RSA, ECDSA and Ed25519.

Includes tests to compare writing out keys produce the exact same private key if read from OpenSSH format and tests against ssh-agent to ensure all no generated key is rejected.
This commit is contained in:
Toni Spets 2021-12-10 20:57:22 +02:00 committed by Jonathan White
parent 714c0a5be2
commit 3243243be8
15 changed files with 1048 additions and 377 deletions

File diff suppressed because it is too large Load Diff

View File

@ -41,6 +41,7 @@
#include "core/TimeDelta.h"
#ifdef WITH_XC_SSHAGENT
#include "sshagent/OpenSSHKey.h"
#include "sshagent/OpenSSHKeyGenDialog.h"
#include "sshagent/SSHAgent.h"
#endif
#ifdef WITH_XC_BROWSER
@ -535,6 +536,7 @@ void EditEntryWidget::updateHistoryButtons(const QModelIndex& current, const QMo
#ifdef WITH_XC_SSHAGENT
void EditEntryWidget::setupSSHAgent()
{
m_pendingPrivateKey = "";
m_sshAgentUi->setupUi(m_sshAgentWidget);
QFont fixedFont = Font::fixedFont();
@ -555,6 +557,7 @@ void EditEntryWidget::setupSSHAgent()
connect(m_sshAgentUi->removeFromAgentButton, &QPushButton::clicked, this, &EditEntryWidget::removeKeyFromAgent);
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_attachments.data(), &EntryAttachments::modified,
this, &EditEntryWidget::updateSSHAgentAttachments);
@ -582,6 +585,12 @@ void EditEntryWidget::updateSSHAgent()
m_sshAgentSettings.fromEntry(m_entry);
setSSHAgentSettings();
if (!m_pendingPrivateKey.isEmpty()) {
m_sshAgentSettings.setAttachmentName(m_pendingPrivateKey);
m_sshAgentSettings.setSelectedType("attachment");
m_pendingPrivateKey = "";
}
updateSSHAgentAttachments();
}
@ -784,6 +793,38 @@ void EditEntryWidget::copyPublicKey()
{
clipboard()->setText(m_sshAgentUi->publicKeyEdit->document()->toPlainText());
}
void EditEntryWidget::generatePrivateKey()
{
auto dialog = new OpenSSHKeyGenDialog(this);
OpenSSHKey key;
dialog->setKey(&key);
if (dialog->exec()) {
// derive openssh naming from type
QString keyPrefix = key.type();
if (keyPrefix.startsWith("ecdsa")) {
keyPrefix = "id_ecdsa";
} else {
keyPrefix.replace("ssh-", "id_");
}
for (int i = 0; i < 10; i++) {
QString keyName = keyPrefix;
if (i > 0) {
keyName += "." + QString::number(i);
}
if (!m_entry->attachments()->hasKey(keyName)) {
m_pendingPrivateKey = keyName;
m_entry->attachments()->set(m_pendingPrivateKey, key.privateKey().toUtf8());
break;
}
}
}
}
#endif
void EditEntryWidget::useExpiryPreset(QAction* action)

View File

@ -123,6 +123,7 @@ private slots:
void removeKeyFromAgent();
void decryptPrivateKey();
void copyPublicKey();
void generatePrivateKey();
#endif
#ifdef WITH_XC_BROWSER
void updateBrowserModified();
@ -167,6 +168,7 @@ private:
bool m_history;
#ifdef WITH_XC_SSHAGENT
KeeAgentSettings m_sshAgentSettings;
QString m_pendingPrivateKey;
#endif
const QScopedPointer<Ui::EditEntryWidgetMain> m_mainUi;
const QScopedPointer<Ui::EditEntryWidgetAdvanced> m_advancedUi;

View File

@ -118,23 +118,6 @@
<string>Private key</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="3" column="0">
<widget class="QRadioButton" name="externalFileRadioButton">
<property name="text">
<string>External file</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QPushButton" name="browseButton">
<property name="accessibleName">
<string>Browser for key file</string>
</property>
<property name="text">
<string extracomment="Button for opening file dialog">Browse…</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QRadioButton" name="attachmentRadioButton">
<property name="text">
@ -145,7 +128,7 @@
</property>
</widget>
</item>
<item row="3" column="1">
<item row="3" column="3">
<widget class="QLineEdit" name="externalFileEdit">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
@ -155,7 +138,7 @@
</property>
</widget>
</item>
<item row="4" column="1">
<item row="4" column="3">
<layout class="QHBoxLayout" name="agentActionsLayout" stretch="0,0">
<item>
<widget class="QPushButton" name="addToAgentButton">
@ -173,7 +156,31 @@
</item>
</layout>
</item>
<item row="0" column="1" colspan="2">
<item row="3" column="0">
<widget class="QRadioButton" name="externalFileRadioButton">
<property name="text">
<string>External file</string>
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QPushButton" name="browseButton">
<property name="accessibleName">
<string>Browser for key file</string>
</property>
<property name="text">
<string extracomment="Button for opening file dialog">Browse…</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QPushButton" name="generateButton">
<property name="text">
<string>Generate</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QComboBox" name="attachmentComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -325,7 +332,6 @@
<tabstop>lifetimeCheckBox</tabstop>
<tabstop>lifetimeSpinBox</tabstop>
<tabstop>attachmentRadioButton</tabstop>
<tabstop>attachmentComboBox</tabstop>
<tabstop>externalFileRadioButton</tabstop>
<tabstop>browseButton</tabstop>
<tabstop>addToAgentButton</tabstop>

View File

@ -8,6 +8,8 @@ if(WITH_XC_SSHAGENT)
BinaryStream.cpp
KeeAgentSettings.cpp
OpenSSHKey.cpp
OpenSSHKeyGen.cpp
OpenSSHKeyGenDialog.cpp
SSHAgent.cpp
)

View File

@ -20,6 +20,7 @@
#include "ASN1Key.h"
#include "BinaryStream.h"
#include "crypto/Random.h"
#include "crypto/SymmetricCipher.h"
#include <QRegularExpression>
@ -34,6 +35,7 @@ const QString OpenSSHKey::OPENSSH_CIPHER_SUFFIX = "@openssh.com";
OpenSSHKey::OpenSSHKey(QObject* parent)
: QObject(parent)
, m_check(0)
, m_type(QString())
, m_cipherName(QString("none"))
, m_kdfName(QString("none"))
@ -49,6 +51,7 @@ OpenSSHKey::OpenSSHKey(QObject* parent)
OpenSSHKey::OpenSSHKey(const OpenSSHKey& other)
: QObject(nullptr)
, m_check(other.m_check)
, m_type(other.m_type)
, m_cipherName(other.m_cipherName)
, m_kdfName(other.m_kdfName)
@ -126,6 +129,64 @@ const QString OpenSSHKey::publicKey() const
return m_type + " " + QString::fromLatin1(publicKey.toBase64()) + " " + m_comment;
}
const QString OpenSSHKey::privateKey()
{
QByteArray sshKey;
BinaryStream stream(&sshKey);
// magic
stream.write(QString("openssh-key-v1").toUtf8());
stream.write(static_cast<quint8>(0));
// cipher name
stream.writeString(QString("none"));
// kdf name
stream.writeString(QString("none"));
// kdf options
stream.writeString(QString(""));
// number of keys
stream.write(static_cast<quint32>(1));
// string wrapped public key
QByteArray publicKey;
BinaryStream publicStream(&publicKey);
writePublic(publicStream);
stream.writeString(publicKey);
// string wrapper private key
QByteArray privateKey;
BinaryStream privateStream(&privateKey);
// integrity check value
privateStream.write(m_check);
privateStream.write(m_check);
writePrivate(privateStream);
// padding for unencrypted key
for (quint8 i = 1; i <= privateKey.size() % 8; i++) {
privateStream.write(i);
}
stream.writeString(privateKey);
// encode to PEM format
QString out;
out += "-----BEGIN OPENSSH PRIVATE KEY-----\n";
auto base64Key = QString::fromUtf8(sshKey.toBase64());
for (int i = 0; i < base64Key.size(); i += 70) {
out += base64Key.midRef(i, 70);
out += "\n";
}
out += "-----END OPENSSH PRIVATE KEY-----\n";
return out;
}
const QString OpenSSHKey::errorString() const
{
return m_error;
@ -136,6 +197,11 @@ void OpenSSHKey::setType(const QString& type)
m_type = type;
}
void OpenSSHKey::setCheck(quint32 check)
{
m_check = check;
}
void OpenSSHKey::setPublicData(const QByteArray& data)
{
m_rawPublicData = data;
@ -429,6 +495,8 @@ bool OpenSSHKey::openKey(const QString& passphrase)
return false;
}
m_check = checkInt1;
return readPrivate(keyStream);
}

View File

@ -41,9 +41,11 @@ public:
const QString fingerprint(QCryptographicHash::Algorithm algo = QCryptographicHash::Sha256) const;
const QString comment() const;
const QString publicKey() const;
const QString privateKey();
const QString errorString() const;
void setType(const QString& type);
void setCheck(quint32 check);
void setPublicData(const QByteArray& data);
void setPrivateData(const QByteArray& data);
void setComment(const QString& comment);
@ -70,6 +72,7 @@ private:
bool extractPEM(const QByteArray& in, QByteArray& out);
quint32 m_check;
QString m_type;
QString m_cipherName;
QByteArray m_cipherIV;

View File

@ -0,0 +1,141 @@
/*
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "OpenSSHKeyGen.h"
#include "BinaryStream.h"
#include "OpenSSHKey.h"
#include "crypto/Random.h"
#include <botan/ecdsa.h>
#include <botan/ed25519.h>
#include <botan/rsa.h>
namespace OpenSSHKeyGen
{
namespace
{
void bigIntToStream(const Botan::BigInt& i, BinaryStream& stream, int padding = 0)
{
QByteArray ba(i.bytes() + padding, 0);
i.binary_encode(reinterpret_cast<uint8_t*>(ba.data() + padding), ba.size() - padding);
stream.writeString(ba);
}
void vectorToStream(const std::vector<uint8_t>& v, BinaryStream& stream)
{
QByteArray ba(reinterpret_cast<const char*>(v.data()), v.size());
stream.writeString(ba);
}
void vectorToStream(const Botan::secure_vector<uint8_t>& v, BinaryStream& stream)
{
QByteArray ba(reinterpret_cast<const char*>(v.data()), v.size());
stream.writeString(ba);
}
} // namespace
bool generateRSA(OpenSSHKey& key, int bits)
{
auto rng = randomGen()->getRng();
try {
Botan::RSA_PrivateKey rsaKey(*rng, bits);
QByteArray publicData;
BinaryStream publicStream(&publicData);
// intentionally flipped n e -> e n
bigIntToStream(rsaKey.get_e(), publicStream);
bigIntToStream(rsaKey.get_n(), publicStream, 1);
QByteArray privateData;
BinaryStream privateStream(&privateData);
bigIntToStream(rsaKey.get_n(), privateStream, 1);
bigIntToStream(rsaKey.get_e(), privateStream);
bigIntToStream(rsaKey.get_d(), privateStream);
bigIntToStream(rsaKey.get_c(), privateStream, 1);
bigIntToStream(rsaKey.get_p(), privateStream, 1);
bigIntToStream(rsaKey.get_q(), privateStream, 1);
key.setType("ssh-rsa");
key.setCheck(randomGen()->randomUInt(std::numeric_limits<quint32>::max() - 1) + 1);
key.setPublicData(publicData);
key.setPrivateData(privateData);
key.setComment("id_rsa");
return true;
} catch (std::exception& e) {
return false;
}
}
bool generateECDSA(OpenSSHKey& key, int bits)
{
auto rng = randomGen()->getRng();
QString group = QString("nistp%1").arg(bits);
try {
Botan::EC_Group domain(QString("secp%1r1").arg(bits).toStdString());
Botan::ECDSA_PrivateKey ecdsaKey(*rng, domain);
QByteArray publicData;
BinaryStream publicStream(&publicData);
publicStream.writeString(group);
vectorToStream(ecdsaKey.public_key_bits(), publicStream);
QByteArray privateData;
BinaryStream privateStream(&privateData);
privateStream.writeString(group);
vectorToStream(ecdsaKey.public_key_bits(), privateStream);
bigIntToStream(ecdsaKey.private_value(), privateStream, 1);
key.setType("ecdsa-sha2-" + group);
key.setCheck(randomGen()->randomUInt(std::numeric_limits<quint32>::max() - 1) + 1);
key.setPublicData(publicData);
key.setPrivateData(privateData);
key.setComment("id_ecdsa");
return true;
} catch (std::exception& e) {
return false;
}
}
bool generateEd25519(OpenSSHKey& key)
{
auto rng = randomGen()->getRng();
try {
Botan::Ed25519_PrivateKey ed25519Key(*rng);
QByteArray publicData;
BinaryStream publicStream(&publicData);
vectorToStream(ed25519Key.get_public_key(), publicStream);
QByteArray privateData;
BinaryStream privateStream(&privateData);
vectorToStream(ed25519Key.get_public_key(), privateStream);
vectorToStream(ed25519Key.get_private_key(), privateStream);
key.setType("ssh-ed25519");
key.setCheck(randomGen()->randomUInt(std::numeric_limits<quint32>::max() - 1) + 1);
key.setPublicData(publicData);
key.setPrivateData(privateData);
key.setComment("id_ed25519");
return true;
} catch (std::exception& e) {
return false;
}
}
} // namespace OpenSSHKeyGen

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_OPENSSHKEYGEN_H
#define KEEPASSXC_OPENSSHKEYGEN_H
class OpenSSHKey;
namespace OpenSSHKeyGen
{
bool generateRSA(OpenSSHKey& key, int bits);
bool generateECDSA(OpenSSHKey& key, int bits);
bool generateEd25519(OpenSSHKey& key);
} // namespace OpenSSHKeyGen
#endif

View File

@ -0,0 +1,99 @@
/*
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "OpenSSHKeyGenDialog.h"
#include "OpenSSHKey.h"
#include "OpenSSHKeyGen.h"
#include "gui/Icons.h"
#include "ui_OpenSSHKeyGenDialog.h"
#include <QHostInfo>
#include <QProcessEnvironment>
OpenSSHKeyGenDialog::OpenSSHKeyGenDialog(QWidget* parent)
: QDialog(parent)
, m_ui(new Ui::OpenSSHKeyGenDialog())
, m_key(nullptr)
{
setAttribute(Qt::WA_DeleteOnClose);
setWindowIcon(icons()->icon("password-generator"));
m_ui->setupUi(this);
m_ui->typeComboBox->clear();
m_ui->typeComboBox->addItem("Ed25519");
m_ui->typeComboBox->addItem("RSA");
m_ui->typeComboBox->addItem("ECDSA");
QString user = QProcessEnvironment::systemEnvironment().value("USER");
m_ui->commentLineEdit->setText(user + "@" + QHostInfo::localHostName());
connect(m_ui->typeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(typeChanged()));
typeChanged();
}
// Required for QScopedPointer
OpenSSHKeyGenDialog::~OpenSSHKeyGenDialog()
{
}
void OpenSSHKeyGenDialog::typeChanged()
{
m_ui->bitsComboBox->clear();
if (m_ui->typeComboBox->currentText() == QString("Ed25519")) {
m_ui->bitsComboBox->addItem("32");
} else if (m_ui->typeComboBox->currentText() == QString("RSA")) {
m_ui->bitsComboBox->addItem("2048");
m_ui->bitsComboBox->addItem("3072");
m_ui->bitsComboBox->addItem("4096");
m_ui->bitsComboBox->setCurrentText("3072");
} else if (m_ui->typeComboBox->currentText() == QString("ECDSA")) {
m_ui->bitsComboBox->addItem("256");
m_ui->bitsComboBox->addItem("384");
m_ui->bitsComboBox->addItem("521");
m_ui->bitsComboBox->setCurrentText("256");
}
}
void OpenSSHKeyGenDialog::accept()
{
// disable form and try to process this update before blocking in key generation
setEnabled(false);
QCoreApplication::processEvents();
int bits = m_ui->bitsComboBox->currentText().toInt();
if (m_ui->typeComboBox->currentText() == QString("Ed25519")) {
OpenSSHKeyGen::generateEd25519(*m_key);
} else if (m_ui->typeComboBox->currentText() == QString("RSA")) {
OpenSSHKeyGen::generateRSA(*m_key, bits);
} else if (m_ui->typeComboBox->currentText() == QString("ECDSA")) {
OpenSSHKeyGen::generateECDSA(*m_key, bits);
} else {
reject();
return;
}
m_key->setComment(m_ui->commentLineEdit->text());
QDialog::accept();
}
void OpenSSHKeyGenDialog::setKey(OpenSSHKey* key)
{
m_key = key;
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2021 Team KeePassXC <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_OPENSSHKEYGENDIALOG_H
#define KEEPASSXC_OPENSSHKEYGENDIALOG_H
#include <QDialog>
class OpenSSHKey;
namespace Ui
{
class OpenSSHKeyGenDialog;
}
class OpenSSHKeyGenDialog : public QDialog
{
Q_OBJECT
public:
explicit OpenSSHKeyGenDialog(QWidget* parent = nullptr);
~OpenSSHKeyGenDialog() override;
void accept() override;
void setKey(OpenSSHKey* key);
private slots:
void typeChanged();
private:
QScopedPointer<Ui::OpenSSHKeyGenDialog> m_ui;
OpenSSHKey* m_key;
};
#endif // KEEPASSXC_OPENSSHKEYGENDIALOG_H

View File

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OpenSSHKeyGenDialog</class>
<widget class="QDialog" name="OpenSSHKeyGenDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>200</width>
<height>100</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>SSH Key Generator</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QComboBox" name="typeComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="typeLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="bitsLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Bits</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="commentLabel">
<property name="text">
<string>Comment</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QComboBox" name="bitsComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
</property>
<property name="minimumContentsLength">
<number>4</number>
</property>
</widget>
</item>
<item row="1" column="1" colspan="3">
<widget class="QLineEdit" name="commentLineEdit"/>
</item>
<item row="2" column="0" colspan="4">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>OpenSSHKeyGenDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>OpenSSHKeyGenDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -254,6 +254,7 @@ void TestOpenSSHKey::testParseRSACompare()
QCOMPARE(oldKey.type(), newKey.type());
QCOMPARE(oldKey.fingerprint(), newKey.fingerprint());
QCOMPARE(oldPrivateKey, newPrivateKey);
QCOMPARE(newKeyString, newKey.privateKey());
}
void TestOpenSSHKey::testParseECDSA256()
@ -277,6 +278,7 @@ void TestOpenSSHKey::testParseECDSA256()
QCOMPARE(key.type(), QString("ecdsa-sha2-nistp256"));
QCOMPARE(key.comment(), QString("opensshkey-test-ecdsa256@keepassxc"));
QCOMPARE(key.fingerprint(), QString("SHA256:nwwovZmQbBeiR3GZRpK4OWHgCUE7E0wFtCN7Ng7eX5g"));
QCOMPARE(keyString, key.privateKey());
}
void TestOpenSSHKey::testParseECDSA384()
@ -302,6 +304,7 @@ void TestOpenSSHKey::testParseECDSA384()
QCOMPARE(key.type(), QString("ecdsa-sha2-nistp384"));
QCOMPARE(key.comment(), QString("opensshkey-test-ecdsa384@keepassxc"));
QCOMPARE(key.fingerprint(), QString("SHA256:B5tLMG976BZ6nyi/oRUmKaTJcaEaFagEjBfOAgru0OY"));
QCOMPARE(keyString, key.privateKey());
}
void TestOpenSSHKey::testParseECDSA521()
@ -328,6 +331,7 @@ void TestOpenSSHKey::testParseECDSA521()
QCOMPARE(key.type(), QString("ecdsa-sha2-nistp521"));
QCOMPARE(key.comment(), QString("opensshkey-test-ecdsa521@keepassxc"));
QCOMPARE(key.fingerprint(), QString("SHA256:m3LtA9MtZW8FN0R3vwA0AAI+YtegbggGCy3EGKWya+s"));
QCOMPARE(keyString, key.privateKey());
}
void TestOpenSSHKey::testDecryptOpenSSHAES256CBC()
@ -533,6 +537,7 @@ void TestOpenSSHKey::testParseECDSASecurityKey()
QCOMPARE(key.type(), QString("sk-ecdsa-sha2-nistp256@openssh.com"));
QCOMPARE(key.comment(), QString("opensshkey-test-ecdsa-sk@keepassxc"));
QCOMPARE(key.fingerprint(), QString("SHA256:ctOtAsPMqbtumGI41o2oeWfGDah4m1ACILRj+x0gx0E"));
QCOMPARE(keyString, key.privateKey());
}
void TestOpenSSHKey::testParseED25519SecurityKey()
@ -557,4 +562,5 @@ void TestOpenSSHKey::testParseED25519SecurityKey()
QCOMPARE(key.type(), QString("sk-ssh-ed25519@openssh.com"));
QCOMPARE(key.comment(), QString("opensshkey-test-ed25519-sk@keepassxc"));
QCOMPARE(key.fingerprint(), QString("SHA256:PGtS5WvbnYmNqFIeRbzO6cVP9GLh8eEzENgkHp02XIA"));
QCOMPARE(keyString, key.privateKey());
}

View File

@ -20,6 +20,7 @@
#include "core/Config.h"
#include "crypto/Crypto.h"
#include "sshagent/KeeAgentSettings.h"
#include "sshagent/OpenSSHKeyGen.h"
#include "sshagent/SSHAgent.h"
#include <QTest>
@ -224,6 +225,66 @@ void TestSSHAgent::testToOpenSSHKey()
QVERIFY(!key.publicKey().isEmpty());
}
void TestSSHAgent::testKeyGenRSA()
{
SSHAgent agent;
agent.setEnabled(true);
agent.setAuthSockOverride(m_agentSocketFileName);
QVERIFY(agent.isAgentRunning());
OpenSSHKey key;
KeeAgentSettings settings;
bool keyInAgent;
QVERIFY(OpenSSHKeyGen::generateRSA(key, 2048));
QVERIFY(agent.addIdentity(key, settings, m_uuid));
QVERIFY(agent.checkIdentity(key, keyInAgent) && keyInAgent);
QVERIFY(agent.removeIdentity(key));
QVERIFY(agent.checkIdentity(key, keyInAgent) && !keyInAgent);
}
void TestSSHAgent::testKeyGenECDSA()
{
SSHAgent agent;
agent.setEnabled(true);
agent.setAuthSockOverride(m_agentSocketFileName);
QVERIFY(agent.isAgentRunning());
OpenSSHKey key;
KeeAgentSettings settings;
bool keyInAgent;
QVERIFY(OpenSSHKeyGen::generateECDSA(key, 256));
QVERIFY(agent.addIdentity(key, settings, m_uuid));
QVERIFY(agent.checkIdentity(key, keyInAgent) && keyInAgent);
QVERIFY(agent.removeIdentity(key));
QVERIFY(agent.checkIdentity(key, keyInAgent) && !keyInAgent);
}
void TestSSHAgent::testKeyGenEd25519()
{
SSHAgent agent;
agent.setEnabled(true);
agent.setAuthSockOverride(m_agentSocketFileName);
QVERIFY(agent.isAgentRunning());
OpenSSHKey key;
KeeAgentSettings settings;
bool keyInAgent;
QVERIFY(OpenSSHKeyGen::generateEd25519(key));
QVERIFY(agent.addIdentity(key, settings, m_uuid));
QVERIFY(agent.checkIdentity(key, keyInAgent) && keyInAgent);
QVERIFY(agent.removeIdentity(key));
QVERIFY(agent.checkIdentity(key, keyInAgent) && !keyInAgent);
}
void TestSSHAgent::cleanupTestCase()
{
if (m_agentProcess.state() != QProcess::NotRunning) {

View File

@ -35,6 +35,9 @@ private slots:
void testLifetimeConstraint();
void testConfirmConstraint();
void testToOpenSSHKey();
void testKeyGenRSA();
void testKeyGenECDSA();
void testKeyGenEd25519();
void cleanupTestCase();
private: