aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Vanbesien <tvanbesi@proton.me>2026-03-31 17:44:35 +0200
committerThomas Vanbesien <tvanbesi@proton.me>2026-03-31 17:44:35 +0200
commitf3beb1624c24012c246d17a40c4e10c1c6b3b5b5 (patch)
tree93430f34b957a6d5037157913605c0ccbba79856
parent6816fc573608cf9a5783caeabd47b8dbe1ac5ac5 (diff)
downloadBobinkQtOpcUa-f3beb1624c24012c246d17a40c4e10c1c6b3b5b5.tar.gz
BobinkQtOpcUa-f3beb1624c24012c246d17a40c4e10c1c6b3b5b5.zip
Add passphrase-protected private key support
Wire up QOpcUaClient::passwordForPrivateKeyRequired to a QML dialog, mirroring the existing certificate trust flow (local QEventLoop + 30s timeout).
-rw-r--r--demo/CMakeLists.txt1
-rw-r--r--demo/KeyPasswordDialog.qml53
-rw-r--r--demo/Main.qml12
-rw-r--r--src/OpcUaClient.cpp37
-rw-r--r--src/OpcUaClient.h17
5 files changed, 120 insertions, 0 deletions
diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt
index 96dade8..ca39de8 100644
--- a/demo/CMakeLists.txt
+++ b/demo/CMakeLists.txt
@@ -9,6 +9,7 @@ qt_add_qml_module(
QML_FILES
Main.qml
CertTrustDialog.qml
+ KeyPasswordDialog.qml
ConnectionPage.qml
DebugConsole.qml
NodePage.qml
diff --git a/demo/KeyPasswordDialog.qml b/demo/KeyPasswordDialog.qml
new file mode 100644
index 0000000..4bf094c
--- /dev/null
+++ b/demo/KeyPasswordDialog.qml
@@ -0,0 +1,53 @@
+// KeyPasswordDialog.qml — Modal dialog for encrypted private-key passphrase.
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Bobink
+
+Dialog {
+ id: keyPasswordDialog
+
+ property string keyFilePath
+ property bool previousTryWasInvalid: false
+
+ anchors.centerIn: parent
+ implicitWidth: 400
+ modal: true
+ standardButtons: Dialog.Ok | Dialog.Cancel
+ title: "Private Key Password"
+
+ onAccepted: Bobink.provideKeyPassword(passwordField.text)
+ onOpened: {
+ passwordField.text = "";
+ passwordField.forceActiveFocus();
+ }
+ onRejected: Bobink.cancelKeyPassword()
+
+ ColumnLayout {
+ width: parent.width
+
+ Label {
+ Layout.fillWidth: true
+ text: "Enter the password for:\n" + keyPasswordDialog.keyFilePath
+ wrapMode: Text.Wrap
+ }
+
+ Label {
+ Layout.fillWidth: true
+ color: "red"
+ text: "Invalid password, please try again."
+ visible: keyPasswordDialog.previousTryWasInvalid
+ }
+
+ TextField {
+ id: passwordField
+
+ Layout.fillWidth: true
+ echoMode: TextInput.Password
+ placeholderText: "Password"
+
+ onAccepted: keyPasswordDialog.accept()
+ }
+ }
+}
diff --git a/demo/Main.qml b/demo/Main.qml
index 908dcbd..f0f3674 100644
--- a/demo/Main.qml
+++ b/demo/Main.qml
@@ -44,6 +44,13 @@ ApplicationWindow {
debugConsole.appendLog("Discovering: " + Bobink.discovering);
}
+ function onPrivateKeyPasswordRequired(keyFilePath,
+ previousTryWasInvalid) {
+ keyPasswordDialog.keyFilePath = keyFilePath;
+ keyPasswordDialog.previousTryWasInvalid = previousTryWasInvalid;
+ keyPasswordDialog.open();
+ }
+
function onServersChanged() {
debugConsole.appendLog("Discovered server list updated");
}
@@ -60,6 +67,11 @@ ApplicationWindow {
}
+ KeyPasswordDialog {
+ id: keyPasswordDialog
+
+ }
+
ColumnLayout {
anchors.fill: parent
spacing: 0
diff --git a/src/OpcUaClient.cpp b/src/OpcUaClient.cpp
index a319f29..d212ab2 100644
--- a/src/OpcUaClient.cpp
+++ b/src/OpcUaClient.cpp
@@ -90,6 +90,8 @@ OpcUaClient::setupClient ()
&OpcUaClient::handleFindServersFinished);
connect (m_client, &QOpcUaClient::errorChanged, this,
&OpcUaClient::handleClientError);
+ connect (m_client, &QOpcUaClient::passwordForPrivateKeyRequired, this,
+ &OpcUaClient::handlePasswordRequired);
}
/* ======================================
@@ -327,6 +329,41 @@ OpcUaClient::handleConnectError (QOpcUaErrorState *errorState)
}
void
+OpcUaClient::provideKeyPassword (const QString &password)
+{
+ m_keyPassword = password;
+ if (m_keyPassLoop)
+ m_keyPassLoop->quit ();
+}
+
+void
+OpcUaClient::cancelKeyPassword ()
+{
+ m_keyPassword.clear ();
+ if (m_keyPassLoop)
+ m_keyPassLoop->quit ();
+}
+
+void
+OpcUaClient::handlePasswordRequired (QString keyFilePath, QString *password,
+ bool previousTryWasInvalid)
+{
+ // passwordForPrivateKeyRequired uses BlockingQueuedConnection — the backend
+ // thread is blocked waiting for us to return. Spin a local event loop so
+ // QML can show a dialog and call provideKeyPassword() / cancelKeyPassword().
+ m_keyPassword.clear ();
+ emit privateKeyPasswordRequired (keyFilePath, previousTryWasInvalid);
+
+ QEventLoop loop;
+ m_keyPassLoop = &loop;
+ QTimer::singleShot (30000, &loop, &QEventLoop::quit);
+ loop.exec ();
+ m_keyPassLoop = nullptr;
+
+ *password = m_keyPassword;
+}
+
+void
OpcUaClient::handleClientError (QOpcUaClient::ClientError error)
{
if (error == QOpcUaClient::NoError)
diff --git a/src/OpcUaClient.h b/src/OpcUaClient.h
index 1476911..7ecd11b 100644
--- a/src/OpcUaClient.h
+++ b/src/OpcUaClient.h
@@ -88,6 +88,11 @@ public:
/** @brief Reject the pending server certificate. */
Q_INVOKABLE void rejectCertificate ();
+ /** @brief Provide the password for an encrypted private key. */
+ Q_INVOKABLE void provideKeyPassword (const QString &password);
+ /** @brief Cancel the private-key password prompt (abort connection). */
+ Q_INVOKABLE void cancelKeyPassword ();
+
/* -- Discovery -- */
QString discoveryUrl () const;
@@ -136,6 +141,14 @@ signals:
void certificateTrustRequested (const QString &certInfo);
void connectionError (const QString &message);
void statusMessage (const QString &message);
+ /**
+ * @brief Emitted when the private key is encrypted.
+ *
+ * The connection blocks until provideKeyPassword() or
+ * cancelKeyPassword() is called (30 s timeout, auto-cancels).
+ */
+ void privateKeyPasswordRequired (const QString &keyFilePath,
+ bool previousTryWasInvalid);
/* -- Discovery -- */
void discoveryUrlChanged ();
@@ -156,6 +169,8 @@ private slots:
const QUrl &requestUrl);
void handleConnectError (QOpcUaErrorState *errorState);
void handleClientError (QOpcUaClient::ClientError error);
+ void handlePasswordRequired (QString keyFilePath, QString *password,
+ bool previousTryWasInvalid);
/* -- Discovery -- */
void handleFindServersFinished (
@@ -174,6 +189,8 @@ private:
bool m_connected = false;
QEventLoop *m_certLoop = nullptr;
bool m_certAccepted = false;
+ QEventLoop *m_keyPassLoop = nullptr;
+ QString m_keyPassword;
/* -- Discovery -- */
QString m_discoveryUrl;