Added a form for entering a passphrase for a private ssh key and the corresponding logic for processing a private key
This commit is contained in:
parent
f6ca22ecdd
commit
f3aef67be6
10 changed files with 161 additions and 8 deletions
|
@ -13,6 +13,7 @@ struct ServerCredentials
|
||||||
QString hostName;
|
QString hostName;
|
||||||
QString userName;
|
QString userName;
|
||||||
QString password;
|
QString password;
|
||||||
|
QString decryptedPrivateKey;
|
||||||
int port = 22;
|
int port = 22;
|
||||||
|
|
||||||
bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !password.isEmpty() && port > 0; }
|
bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !password.isEmpty() && port > 0; }
|
||||||
|
|
|
@ -46,7 +46,7 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
|
||||||
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdOut,
|
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdOut,
|
||||||
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdErr) {
|
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdErr) {
|
||||||
|
|
||||||
auto error = m_sshClient.connectToHost(credentials);
|
auto error = m_sshClient.connectToHost(credentials, m_passphraseCallback);
|
||||||
if (error != ErrorCode::NoError) {
|
if (error != ErrorCode::NoError) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const
|
||||||
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
|
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
|
||||||
libssh::SftpOverwriteMode overwriteMode)
|
libssh::SftpOverwriteMode overwriteMode)
|
||||||
{
|
{
|
||||||
auto error = m_sshClient.connectToHost(credentials);
|
auto error = m_sshClient.connectToHost(credentials, m_passphraseCallback);
|
||||||
if (error != ErrorCode::NoError) {
|
if (error != ErrorCode::NoError) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
@ -754,3 +754,8 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential
|
||||||
|
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServerController::setPassphraseCallback(const std::function<QString()> &callback)
|
||||||
|
{
|
||||||
|
m_passphraseCallback = callback;
|
||||||
|
}
|
||||||
|
|
|
@ -72,6 +72,8 @@ public:
|
||||||
|
|
||||||
void setCancelInstallation(const bool cancel);
|
void setCancelInstallation(const bool cancel);
|
||||||
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, QJsonObject> &installedContainers);
|
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, QJsonObject> &installedContainers);
|
||||||
|
|
||||||
|
void setPassphraseCallback(const std::function<QString()> &callback);
|
||||||
private:
|
private:
|
||||||
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
|
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
|
||||||
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||||
|
@ -86,6 +88,7 @@ private:
|
||||||
|
|
||||||
bool m_cancelInstallation = false;
|
bool m_cancelInstallation = false;
|
||||||
libssh::Client m_sshClient;
|
libssh::Client m_sshClient;
|
||||||
|
std::function<QString()> m_passphraseCallback;
|
||||||
signals:
|
signals:
|
||||||
void serverIsBusy(const bool isBusy);
|
void serverIsBusy(const bool isBusy);
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,18 +10,30 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace libssh {
|
namespace libssh {
|
||||||
|
std::function<QString()> Client::m_passphraseCallback;
|
||||||
|
|
||||||
Client::Client(QObject *parent) : QObject(parent)
|
Client::Client(QObject *parent) : QObject(parent)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
Client::~Client()
|
Client::~Client()
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
ErrorCode Client::connectToHost(const ServerCredentials &credentials)
|
int Client::callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata)
|
||||||
|
{
|
||||||
|
auto passphrase = m_passphraseCallback();
|
||||||
|
passphrase.toStdString().copy(buf, passphrase.size() + 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode Client::connectToHost(const ServerCredentials &credentials, const std::function<QString()> &passphraseCallback)
|
||||||
{
|
{
|
||||||
// if (is_ssh_initialized()) {
|
// if (is_ssh_initialized()) {
|
||||||
// qDebug() << "Failed to initialize ssh";
|
// qDebug() << "Failed to initialize ssh";
|
||||||
// return ErrorCode::InternalError;
|
// return ErrorCode::InternalError;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
m_passphraseCallback = passphraseCallback;
|
||||||
|
|
||||||
if (m_session == nullptr) {
|
if (m_session == nullptr) {
|
||||||
m_session = ssh_new();
|
m_session = ssh_new();
|
||||||
|
|
||||||
|
@ -52,10 +64,42 @@ namespace libssh {
|
||||||
int authResult = SSH_ERROR;
|
int authResult = SSH_ERROR;
|
||||||
if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) {
|
if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) {
|
||||||
ssh_key privateKey;
|
ssh_key privateKey;
|
||||||
ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, nullptr, nullptr, &privateKey);
|
authResult = ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, callback, nullptr, &privateKey);
|
||||||
authResult = ssh_userauth_publickey(m_session, authUsername.c_str(), privateKey);
|
if (authResult != SSH_OK) {
|
||||||
|
qDebug() << ssh_get_error(m_session);
|
||||||
|
return fromLibsshErrorCode(ssh_get_error_code(m_session));
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
ssh_key publicKey;
|
||||||
|
authResult = ssh_pki_export_privkey_to_pubkey(privateKey, &publicKey);
|
||||||
|
if (authResult != SSH_OK) {
|
||||||
|
qDebug() << ssh_get_error(m_session);
|
||||||
|
return fromLibsshErrorCode(ssh_get_error_code(m_session));
|
||||||
|
}
|
||||||
|
authResult = ssh_userauth_try_publickey(m_session, authUsername.c_str(), publicKey);
|
||||||
|
if (authResult != SSH_OK) {
|
||||||
|
qDebug() << ssh_get_error(m_session);
|
||||||
|
return fromLibsshErrorCode(ssh_get_error_code(m_session));
|
||||||
|
}
|
||||||
|
|
||||||
|
authResult = ssh_userauth_publickey(m_session, authUsername.c_str(), privateKey);
|
||||||
|
if (authResult != SSH_OK) {
|
||||||
|
qDebug() << ssh_get_error(m_session);
|
||||||
|
return fromLibsshErrorCode(ssh_get_error_code(m_session));
|
||||||
|
}
|
||||||
|
|
||||||
|
char* key = new char[65535];
|
||||||
|
authResult = ssh_pki_export_privkey_base64(privateKey, nullptr, nullptr, nullptr, &key);
|
||||||
|
if (authResult != SSH_OK) {
|
||||||
|
qDebug() << ssh_get_error(m_session);
|
||||||
|
return fromLibsshErrorCode(ssh_get_error_code(m_session));
|
||||||
|
}
|
||||||
|
|
||||||
|
// credentials.decryptedPrivateKey(key);
|
||||||
|
|
||||||
|
ssh_key_free(publicKey);
|
||||||
|
ssh_key_free(privateKey);
|
||||||
|
} else {
|
||||||
authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.password.toStdString().c_str());
|
authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.password.toStdString().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace libssh {
|
||||||
Client(QObject *parent = nullptr);
|
Client(QObject *parent = nullptr);
|
||||||
~Client();
|
~Client();
|
||||||
|
|
||||||
ErrorCode connectToHost(const ServerCredentials &credentials);
|
ErrorCode connectToHost(const ServerCredentials &credentials, const std::function<QString()> &passphraseCallback);
|
||||||
void disconnectFromHost();
|
void disconnectFromHost();
|
||||||
ErrorCode executeCommand(const QString &data,
|
ErrorCode executeCommand(const QString &data,
|
||||||
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdOut,
|
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdOut,
|
||||||
|
@ -41,10 +41,13 @@ namespace libssh {
|
||||||
ErrorCode closeSftpSession();
|
ErrorCode closeSftpSession();
|
||||||
ErrorCode fromLibsshErrorCode(int errorCode);
|
ErrorCode fromLibsshErrorCode(int errorCode);
|
||||||
ErrorCode fromLibsshSftpErrorCode(int errorCode);
|
ErrorCode fromLibsshSftpErrorCode(int errorCode);
|
||||||
|
static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata);
|
||||||
|
|
||||||
ssh_session m_session = nullptr;
|
ssh_session m_session = nullptr;
|
||||||
ssh_channel m_channel = nullptr;
|
ssh_channel m_channel = nullptr;
|
||||||
sftp_session m_sftpSession = nullptr;
|
sftp_session m_sftpSession = nullptr;
|
||||||
|
|
||||||
|
static std::function<QString()> m_passphraseCallback;
|
||||||
signals:
|
signals:
|
||||||
void writeToChannelFinished();
|
void writeToChannelFinished();
|
||||||
void sftpFileCopyFinished();
|
void sftpFileCopyFinished();
|
||||||
|
|
|
@ -165,5 +165,6 @@
|
||||||
<file>ui/qml/Controls/PopupWithQuestion.qml</file>
|
<file>ui/qml/Controls/PopupWithQuestion.qml</file>
|
||||||
<file>ui/qml/Pages/PageAdvancedServerSettings.qml</file>
|
<file>ui/qml/Pages/PageAdvancedServerSettings.qml</file>
|
||||||
<file>ui/qml/Controls/PopupWarning.qml</file>
|
<file>ui/qml/Controls/PopupWarning.qml</file>
|
||||||
|
<file>ui/qml/Controls/PopupWithInputField.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
62
client/ui/qml/Controls/PopupWithInputField.qml
Normal file
62
client/ui/qml/Controls/PopupWithInputField.qml
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property alias text: textField.text
|
||||||
|
property alias placeholderText: textField.placeholderText
|
||||||
|
property string yesText: "yes"
|
||||||
|
property string noText: "no"
|
||||||
|
property var yesFunc
|
||||||
|
property var noFunc
|
||||||
|
|
||||||
|
signal editingFinished()
|
||||||
|
|
||||||
|
anchors.centerIn: Overlay.overlay
|
||||||
|
modal: true
|
||||||
|
closePolicy: Popup.NoAutoClose
|
||||||
|
|
||||||
|
width: parent.width - 20
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: textField
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font.pixelSize: 16
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
BlueButtonType {
|
||||||
|
id: yesButton
|
||||||
|
Layout.preferredWidth: parent.width / 2
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: yesText
|
||||||
|
onClicked: {
|
||||||
|
root.enabled = false
|
||||||
|
if (yesFunc && typeof yesFunc === "function") {
|
||||||
|
yesFunc()
|
||||||
|
}
|
||||||
|
root.enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlueButtonType {
|
||||||
|
id: noButton
|
||||||
|
Layout.preferredWidth: parent.width / 2
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: noText
|
||||||
|
onClicked: {
|
||||||
|
if (noFunc && typeof noFunc === "function") {
|
||||||
|
noFunc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -234,6 +234,9 @@ Window {
|
||||||
popupWarning.popupWarningText = message
|
popupWarning.popupWarningText = message
|
||||||
popupWarning.open()
|
popupWarning.open()
|
||||||
}
|
}
|
||||||
|
function onShowPassphraseRequestMessage() {
|
||||||
|
popupWithInputField.open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
MessageDialog {
|
||||||
|
@ -355,4 +358,21 @@ Window {
|
||||||
PopupWarning {
|
PopupWarning {
|
||||||
id: popupWarning
|
id: popupWarning
|
||||||
}
|
}
|
||||||
|
PopupWithInputField {
|
||||||
|
id: popupWithInputField
|
||||||
|
placeholderText: "Enter private key passphrase"
|
||||||
|
yesFunc: function() {
|
||||||
|
editingFinished()
|
||||||
|
close()
|
||||||
|
UiLogic.passphraseDialogClosed()
|
||||||
|
text = ""
|
||||||
|
}
|
||||||
|
noFunc: function() {
|
||||||
|
close()
|
||||||
|
UiLogic.passphraseDialogClosed()
|
||||||
|
}
|
||||||
|
onEditingFinished: {
|
||||||
|
UiLogic.privateKeyPassphrase = text
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,16 @@ void UiLogic::initalizeUiLogic()
|
||||||
connect(m_notificationHandler, &NotificationHandler::connectRequested, pageLogic<VpnLogic>(), &VpnLogic::onConnect);
|
connect(m_notificationHandler, &NotificationHandler::connectRequested, pageLogic<VpnLogic>(), &VpnLogic::onConnect);
|
||||||
connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic<VpnLogic>(), &VpnLogic::onDisconnect);
|
connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic<VpnLogic>(), &VpnLogic::onDisconnect);
|
||||||
|
|
||||||
|
auto passphraseCallback = [this]() {
|
||||||
|
emit showPassphraseRequestMessage();
|
||||||
|
QEventLoop loop;
|
||||||
|
QObject::connect(this, &UiLogic::passphraseDialogClosed, &loop, &QEventLoop::quit);
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
return m_privateKeyPassphrase;
|
||||||
|
};
|
||||||
|
m_serverController->setPassphraseCallback(passphraseCallback);
|
||||||
|
|
||||||
if (m_settings->serversCount() > 0) {
|
if (m_settings->serversCount() > 0) {
|
||||||
if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0);
|
if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0);
|
||||||
emit goToPage(Page::Vpn, true, false);
|
emit goToPage(Page::Vpn, true, false);
|
||||||
|
@ -576,3 +586,4 @@ bool UiLogic::isContainerAlreadyAddedToGui(DockerContainer container)
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ class UiLogic : public QObject
|
||||||
AUTO_PROPERTY(bool, pageEnabled)
|
AUTO_PROPERTY(bool, pageEnabled)
|
||||||
AUTO_PROPERTY(int, pagesStackDepth)
|
AUTO_PROPERTY(int, pagesStackDepth)
|
||||||
AUTO_PROPERTY(int, currentPageValue)
|
AUTO_PROPERTY(int, currentPageValue)
|
||||||
AUTO_PROPERTY(QString, popupWarningText)
|
AUTO_PROPERTY(QString, privateKeyPassphrase);
|
||||||
|
|
||||||
READONLY_PROPERTY(QObject *, containersModel)
|
READONLY_PROPERTY(QObject *, containersModel)
|
||||||
READONLY_PROPERTY(QObject *, protocolsModel)
|
READONLY_PROPERTY(QObject *, protocolsModel)
|
||||||
|
@ -136,6 +136,9 @@ signals:
|
||||||
void toggleLogPanel();
|
void toggleLogPanel();
|
||||||
void showWarningMessage(QString message);
|
void showWarningMessage(QString message);
|
||||||
|
|
||||||
|
void showPassphraseRequestMessage();
|
||||||
|
void passphraseDialogClosed();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
// containers - INOUT arg
|
// containers - INOUT arg
|
||||||
void installServer(QPair<amnezia::DockerContainer, QJsonObject> &container);
|
void installServer(QPair<amnezia::DockerContainer, QJsonObject> &container);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue