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:
vladimir.kuznetsov 2023-04-02 09:09:20 +03:00
parent f6ca22ecdd
commit f3aef67be6
10 changed files with 161 additions and 8 deletions

View file

@ -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; }

View file

@ -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 &)> &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<QString()> &callback)
{
m_passphraseCallback = callback;
}

View file

@ -72,6 +72,8 @@ public:
void setCancelInstallation(const bool cancel);
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, QJsonObject> &installedContainers);
void setPassphraseCallback(const std::function<QString()> &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<QString()> m_passphraseCallback;
signals:
void serverIsBusy(const bool isBusy);
};

View file

@ -10,18 +10,30 @@
#endif
namespace libssh {
std::function<QString()> 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<QString()> &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());
}

View file

@ -26,7 +26,7 @@ namespace libssh {
Client(QObject *parent = nullptr);
~Client();
ErrorCode connectToHost(const ServerCredentials &credentials);
ErrorCode connectToHost(const ServerCredentials &credentials, const std::function<QString()> &passphraseCallback);
void disconnectFromHost();
ErrorCode executeCommand(const QString &data,
const std::function<ErrorCode (const QString &, Client &)> &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<QString()> m_passphraseCallback;
signals:
void writeToChannelFinished();
void sftpFileCopyFinished();

View file

@ -165,5 +165,6 @@
<file>ui/qml/Controls/PopupWithQuestion.qml</file>
<file>ui/qml/Pages/PageAdvancedServerSettings.qml</file>
<file>ui/qml/Controls/PopupWarning.qml</file>
<file>ui/qml/Controls/PopupWithInputField.qml</file>
</qresource>
</RCC>

View 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()
}
}
}
}
}
}

View file

@ -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
}
}
}

View file

@ -149,6 +149,16 @@ void UiLogic::initalizeUiLogic()
connect(m_notificationHandler, &NotificationHandler::connectRequested, pageLogic<VpnLogic>(), &VpnLogic::onConnect);
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->defaultServerIndex() < 0) m_settings->setDefaultServer(0);
emit goToPage(Page::Vpn, true, false);
@ -576,3 +586,4 @@ bool UiLogic::isContainerAlreadyAddedToGui(DockerContainer container)
}
return false;
}

View file

@ -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<amnezia::DockerContainer, QJsonObject> &container);