diff --git a/client/core/defs.h b/client/core/defs.h index 125c3101..9017fbd3 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -13,6 +13,7 @@ struct ServerCredentials QString hostName; QString userName; QString password; + QString decryptedPrivateKey; int port = 22; bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !password.isEmpty() && port > 0; } diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 4cf0a2d0..eb9305a2 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -46,7 +46,7 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr const std::function &cbReadStdOut, const std::function &cbReadStdErr) { - auto error = m_sshClient.connectToHost(credentials); + auto error = m_sshClient.connectToHost(credentials, m_passphraseCallback); if (error != ErrorCode::NoError) { return error; } @@ -221,7 +221,7 @@ ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, libssh::SftpOverwriteMode overwriteMode) { - auto error = m_sshClient.connectToHost(credentials); + auto error = m_sshClient.connectToHost(credentials, m_passphraseCallback); if (error != ErrorCode::NoError) { return error; } @@ -754,3 +754,8 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential return ErrorCode::NoError; } + +void ServerController::setPassphraseCallback(const std::function &callback) +{ + m_passphraseCallback = callback; +} diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index fc16d081..6b1a4793 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -72,6 +72,8 @@ public: void setCancelInstallation(const bool cancel); ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers); + + void setPassphraseCallback(const std::function &callback); private: ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); @@ -86,6 +88,7 @@ private: bool m_cancelInstallation = false; libssh::Client m_sshClient; + std::function m_passphraseCallback; signals: void serverIsBusy(const bool isBusy); }; diff --git a/client/core/sshclient.cpp b/client/core/sshclient.cpp index bdb396c3..1523257e 100644 --- a/client/core/sshclient.cpp +++ b/client/core/sshclient.cpp @@ -10,18 +10,30 @@ #endif namespace libssh { + std::function Client::m_passphraseCallback; + Client::Client(QObject *parent) : QObject(parent) { } 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 &passphraseCallback) { // if (is_ssh_initialized()) { // qDebug() << "Failed to initialize ssh"; // return ErrorCode::InternalError; // } + + m_passphraseCallback = passphraseCallback; + if (m_session == nullptr) { m_session = ssh_new(); @@ -52,10 +64,42 @@ namespace libssh { int authResult = SSH_ERROR; if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) { 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); + if (authResult != SSH_OK) { + qDebug() << ssh_get_error(m_session); + return fromLibsshErrorCode(ssh_get_error_code(m_session)); + } + + 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); - } - else { + 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()); } diff --git a/client/core/sshclient.h b/client/core/sshclient.h index 912748f0..057c72fe 100644 --- a/client/core/sshclient.h +++ b/client/core/sshclient.h @@ -26,7 +26,7 @@ namespace libssh { Client(QObject *parent = nullptr); ~Client(); - ErrorCode connectToHost(const ServerCredentials &credentials); + ErrorCode connectToHost(const ServerCredentials &credentials, const std::function &passphraseCallback); void disconnectFromHost(); ErrorCode executeCommand(const QString &data, const std::function &cbReadStdOut, @@ -41,10 +41,13 @@ namespace libssh { ErrorCode closeSftpSession(); ErrorCode fromLibsshErrorCode(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_channel m_channel = nullptr; sftp_session m_sftpSession = nullptr; + + static std::function m_passphraseCallback; signals: void writeToChannelFinished(); void sftpFileCopyFinished(); diff --git a/client/resources.qrc b/client/resources.qrc index 67937bda..ccc6ad68 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -165,5 +165,6 @@ ui/qml/Controls/PopupWithQuestion.qml ui/qml/Pages/PageAdvancedServerSettings.qml ui/qml/Controls/PopupWarning.qml + ui/qml/Controls/PopupWithInputField.qml diff --git a/client/ui/qml/Controls/PopupWithInputField.qml b/client/ui/qml/Controls/PopupWithInputField.qml new file mode 100644 index 00000000..acdf1247 --- /dev/null +++ b/client/ui/qml/Controls/PopupWithInputField.qml @@ -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() + } + } + } + } + } +} diff --git a/client/ui/qml/main.qml b/client/ui/qml/main.qml index b8758384..8a23d7a2 100644 --- a/client/ui/qml/main.qml +++ b/client/ui/qml/main.qml @@ -234,6 +234,9 @@ Window { popupWarning.popupWarningText = message popupWarning.open() } + function onShowPassphraseRequestMessage() { + popupWithInputField.open() + } } MessageDialog { @@ -355,4 +358,21 @@ Window { 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 + } + } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index 895a526c..527dc1a6 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -149,6 +149,16 @@ void UiLogic::initalizeUiLogic() connect(m_notificationHandler, &NotificationHandler::connectRequested, pageLogic(), &VpnLogic::onConnect); connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic(), &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->defaultServerIndex() < 0) m_settings->setDefaultServer(0); emit goToPage(Page::Vpn, true, false); @@ -576,3 +586,4 @@ bool UiLogic::isContainerAlreadyAddedToGui(DockerContainer container) } return false; } + diff --git a/client/ui/uilogic.h b/client/ui/uilogic.h index 92035fe5..ae7074e6 100644 --- a/client/ui/uilogic.h +++ b/client/ui/uilogic.h @@ -62,7 +62,7 @@ class UiLogic : public QObject AUTO_PROPERTY(bool, pageEnabled) AUTO_PROPERTY(int, pagesStackDepth) AUTO_PROPERTY(int, currentPageValue) - AUTO_PROPERTY(QString, popupWarningText) + AUTO_PROPERTY(QString, privateKeyPassphrase); READONLY_PROPERTY(QObject *, containersModel) READONLY_PROPERTY(QObject *, protocolsModel) @@ -136,6 +136,9 @@ signals: void toggleLogPanel(); void showWarningMessage(QString message); + void showPassphraseRequestMessage(); + void passphraseDialogClosed(); + private slots: // containers - INOUT arg void installServer(QPair &container);