mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
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:
parent
714c0a5be2
commit
3243243be8
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -8,6 +8,8 @@ if(WITH_XC_SSHAGENT)
|
||||
BinaryStream.cpp
|
||||
KeeAgentSettings.cpp
|
||||
OpenSSHKey.cpp
|
||||
OpenSSHKeyGen.cpp
|
||||
OpenSSHKeyGenDialog.cpp
|
||||
SSHAgent.cpp
|
||||
)
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
141
src/sshagent/OpenSSHKeyGen.cpp
Normal file
141
src/sshagent/OpenSSHKeyGen.cpp
Normal 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
|
30
src/sshagent/OpenSSHKeyGen.h
Normal file
30
src/sshagent/OpenSSHKeyGen.h
Normal 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
|
99
src/sshagent/OpenSSHKeyGenDialog.cpp
Normal file
99
src/sshagent/OpenSSHKeyGenDialog.cpp
Normal 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;
|
||||
}
|
48
src/sshagent/OpenSSHKeyGenDialog.h
Normal file
48
src/sshagent/OpenSSHKeyGenDialog.h
Normal 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
|
138
src/sshagent/OpenSSHKeyGenDialog.ui
Normal file
138
src/sshagent/OpenSSHKeyGenDialog.ui
Normal 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>
|
@ -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());
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -35,6 +35,9 @@ private slots:
|
||||
void testLifetimeConstraint();
|
||||
void testConfirmConstraint();
|
||||
void testToOpenSSHKey();
|
||||
void testKeyGenRSA();
|
||||
void testKeyGenECDSA();
|
||||
void testKeyGenEd25519();
|
||||
void cleanupTestCase();
|
||||
|
||||
private:
|
||||
|
Loading…
Reference in New Issue
Block a user