diff --git a/.github/scripts/Build-Deps-Windows.ps1 b/.github/scripts/Build-Deps-Windows.ps1 index a911ef91..a9c2138e 100644 --- a/.github/scripts/Build-Deps-Windows.ps1 +++ b/.github/scripts/Build-Deps-Windows.ps1 @@ -234,6 +234,7 @@ function Build { "-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}" "-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}" "-DPAHO_WITH_MQTT_C=ON" + "-DPAHO_WITH_SSL=ON" ) Log-Information "Configuring paho.mqtt.cpp..." diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 35b85427..af72f1e2 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -1514,6 +1514,13 @@ AdvSceneSwitcher.mqttConnection.name="Name:" AdvSceneSwitcher.mqttConnection.address="Address:" AdvSceneSwitcher.mqttConnection.username="Username:" AdvSceneSwitcher.mqttConnection.password="Password:" +AdvSceneSwitcher.mqttConnection.trustStore="Trust store:" +AdvSceneSwitcher.mqttConnection.trustStore.help="The file in PEM format containing the public digital certificates trusted by the client." +AdvSceneSwitcher.mqttConnection.keyStore="Key store:" +AdvSceneSwitcher.mqttConnection.keyStore.help="The file in PEM format containing the public certificate chain of the client.\nIt may also include the client's private key." +AdvSceneSwitcher.mqttConnection.privateKey="Private key:" +AdvSceneSwitcher.mqttConnection.privateKey.help="If not included in the key store, this is the file in PEM format containing the client's private key." +AdvSceneSwitcher.mqttConnection.verifyServerCert="Verify server certificate" AdvSceneSwitcher.mqttConnection.reconnect="Reconnect automatically:" AdvSceneSwitcher.mqttConnection.reconnectDelay="Automatically reconnect after:" AdvSceneSwitcher.mqttConnection.connectOnStart="Connect on startup:" diff --git a/plugins/mqtt/mqtt-helpers.cpp b/plugins/mqtt/mqtt-helpers.cpp index 2be2cf21..bdb3ec2c 100644 --- a/plugins/mqtt/mqtt-helpers.cpp +++ b/plugins/mqtt/mqtt-helpers.cpp @@ -1,4 +1,5 @@ #include "mqtt-helpers.hpp" +#include "help-icon.hpp" #include "layout-helpers.hpp" #include "log-helper.hpp" #include "obs-module-helper.hpp" @@ -22,6 +23,10 @@ MqttConnection::MqttConnection(const MqttConnection &other) _uri(other._uri), _username(other._username), _password(other._password), + _trustStore(other._trustStore), + _keyStore(other._keyStore), + _privateKey(other._privateKey), + _verifyServerCert(other._verifyServerCert), _connectOnStart(other._connectOnStart), _reconnect(other._reconnect), _reconnectDelay(other._reconnectDelay) @@ -30,12 +35,20 @@ MqttConnection::MqttConnection(const MqttConnection &other) MqttConnection::MqttConnection(const std::string &name, const std::string &uri, const std::string &username, - const std::string &password, bool connectOnStart, + const std::string &password, + const std::string &trustStore, + const std::string &keyStore, + const std::string &privateKey, + bool verifyServerCert, bool connectOnStart, bool reconnect, int reconnectDelay) : Item(name), _uri(uri), _username(username), _password(password), + _trustStore(trustStore), + _keyStore(keyStore), + _privateKey(privateKey), + _verifyServerCert(verifyServerCert), _connectOnStart(connectOnStart), _reconnect(reconnect), _reconnectDelay(reconnectDelay) @@ -99,26 +112,41 @@ void MqttConnection::ConnectThread() }; do { - std::unique_lock clientLock(_clientMtx); - _client = std::make_shared( - _uri, std::string("advss_") + _name); -#ifdef ENABLE_MQTT5_SUPPORT - auto connOpts = mqtt::connect_options_builder::v5() -#else - auto connOpts = mqtt::connect_options_builder() -#endif - .clean_start(false) - .clean_session(true) - .connect_timeout(5s) - .user_name(_username) - .password(_password) - .finalize(); - - _client->set_connection_lost_handler(logConnectionLost); - _client->set_message_callback(dispatchMessage); - _client->start_consuming(); - try { + std::unique_lock clientLock(_clientMtx); + _client = std::make_shared( + _uri, std::string("advss_") + _name); + + mqtt::ssl_options sslOptions; + if (!_trustStore.empty()) { + sslOptions.set_trust_store(_trustStore); + } + if (!_keyStore.empty()) { + sslOptions.set_key_store(_keyStore); + } + if (!_privateKey.empty()) { + sslOptions.set_private_key(_privateKey); + } + sslOptions.set_enable_server_cert_auth( + _verifyServerCert); + +#ifdef ENABLE_MQTT5_SUPPORT + auto connOpts = mqtt::connect_options_builder::v5() +#else + auto connOpts = mqtt::connect_options_builder() +#endif + .clean_start(false) + .clean_session(true) + .connect_timeout(5s) + .user_name(_username) + .password(_password) + .ssl(sslOptions) + .finalize(); + + _client->set_connection_lost_handler(logConnectionLost); + _client->set_message_callback(dispatchMessage); + _client->start_consuming(); + vblog(LOG_INFO, "connecting to MQTT server \"%s\" ...", _name.c_str()); auto tok = _client->connect(connOpts); @@ -220,6 +248,10 @@ void MqttConnection::Load(obs_data_t *data) _uri = obs_data_get_string(data, "uri"); _username = obs_data_get_string(data, "username"); _password = obs_data_get_string(data, "password"); + _trustStore = obs_data_get_string(data, "trustStore"); + _keyStore = obs_data_get_string(data, "keyStore"); + _privateKey = obs_data_get_string(data, "privateKey"); + _verifyServerCert = obs_data_get_bool(data, "verifyServerCert"); _connectOnStart = obs_data_get_bool(data, "connectOnStart"); _reconnect = obs_data_get_bool(data, "reconnect"); _reconnectDelay = obs_data_get_int(data, "reconnectDelay"); @@ -251,6 +283,10 @@ void MqttConnection::Save(obs_data_t *data) const obs_data_set_string(data, "uri", _uri.c_str()); obs_data_set_string(data, "username", _username.c_str()); obs_data_set_string(data, "password", _password.c_str()); + obs_data_set_string(data, "trustStore", _trustStore.c_str()); + obs_data_set_string(data, "keyStore", _keyStore.c_str()); + obs_data_set_string(data, "privateKey", _privateKey.c_str()); + obs_data_set_bool(data, "verifyServerCert", _verifyServerCert); obs_data_set_bool(data, "connectOnStart", _connectOnStart); obs_data_set_bool(data, "reconnect", _reconnect); obs_data_set_int(data, "reconnectDelay", _reconnectDelay); @@ -308,6 +344,10 @@ MqttConnectionSettingsDialog::MqttConnectionSettingsDialog( _username(new QLineEdit()), _password(new QLineEdit()), _showPassword(new QPushButton()), + _trustStore(new FileSelection()), + _keyStore(new FileSelection()), + _privateKey(new FileSelection()), + _verifyServerCert(new QCheckBox()), _topics(new MqttTopicListWidget(this)), _connectOnStart(new QCheckBox()), _reconnect(new QCheckBox()), @@ -324,6 +364,10 @@ MqttConnectionSettingsDialog::MqttConnectionSettingsDialog( _uri->setText(QString::fromStdString(connection._uri)); _username->setText(QString::fromStdString(connection._username)); _password->setText(QString::fromStdString(connection._password)); + _trustStore->SetPath(QString::fromStdString(connection._trustStore)); + _keyStore->SetPath(QString::fromStdString(connection._keyStore)); + _privateKey->SetPath(QString::fromStdString(connection._privateKey)); + _verifyServerCert->setChecked(connection._verifyServerCert); _topics->SetValues(connection._topics, connection._qos); _reconnectDelay->setMaximum(9999); _reconnectDelay->setSuffix("s"); @@ -364,6 +408,41 @@ MqttConnectionSettingsDialog::MqttConnectionSettingsDialog( passLayout->addWidget(_showPassword); _layout->addLayout(passLayout, row, 1); ++row; + _layout->addWidget( + new QLabel(obs_module_text( + "AdvSceneSwitcher.mqttConnection.trustStore")), + row, 0); + auto trustStoreLayout = new QHBoxLayout(); + trustStoreLayout->addWidget(_trustStore); + trustStoreLayout->addWidget(new HelpIcon(obs_module_text( + "AdvSceneSwitcher.mqttConnection.trustStore.help"))); + _layout->addLayout(trustStoreLayout, row, 1); + ++row; + _layout->addWidget(new QLabel(obs_module_text( + "AdvSceneSwitcher.mqttConnection.keyStore")), + row, 0); + auto keyStoreLayout = new QHBoxLayout(); + keyStoreLayout->addWidget(_keyStore); + keyStoreLayout->addWidget(new HelpIcon(obs_module_text( + "AdvSceneSwitcher.mqttConnection.keyStore.help"))); + _layout->addLayout(keyStoreLayout, row, 1); + ++row; + _layout->addWidget( + new QLabel(obs_module_text( + "AdvSceneSwitcher.mqttConnection.privateKey")), + row, 0); + auto privateKeyLayout = new QHBoxLayout(); + privateKeyLayout->addWidget(_privateKey); + privateKeyLayout->addWidget(new HelpIcon(obs_module_text( + "AdvSceneSwitcher.mqttConnection.privateKey.help"))); + _layout->addLayout(privateKeyLayout, row, 1); + ++row; + _layout->addWidget( + new QLabel(obs_module_text( + "AdvSceneSwitcher.mqttConnection.verifyServerCert")), + row, 0); + _layout->addWidget(_verifyServerCert, row, 1); + ++row; _layout->addWidget(new QLabel( obs_module_text("AdvSceneSwitcher.mqttConnection.topics"))); ++row; @@ -412,6 +491,10 @@ bool MqttConnectionSettingsDialog::AskForSettings(QWidget *parent, connection._uri = dialog._uri->text().toStdString(); connection._username = dialog._username->text().toStdString(); connection._password = dialog._password->text().toStdString(); + connection._trustStore = dialog._trustStore->GetPath().toStdString(); + connection._keyStore = dialog._keyStore->GetPath().toStdString(); + connection._privateKey = dialog._privateKey->GetPath().toStdString(); + connection._verifyServerCert = dialog._verifyServerCert->isChecked(); connection._topics = dialog._topics->GetTopics(); connection._qos = dialog._topics->GetQoS(); connection._connectOnStart = dialog._connectOnStart->isChecked(); @@ -455,6 +538,10 @@ void MqttConnectionSettingsDialog::TestConnection() connection->_uri = _uri->text().toStdString(); connection->_username = _username->text().toStdString(); connection->_password = _password->text().toStdString(); + connection->_trustStore = _trustStore->GetPath().toStdString(); + connection->_keyStore = _keyStore->GetPath().toStdString(); + connection->_privateKey = _privateKey->GetPath().toStdString(); + connection->_verifyServerCert = _verifyServerCert->isChecked(); connection->_topics = _topics->GetTopics(); connection->_qos = _topics->GetQoS(); connection->_connectOnStart = false; diff --git a/plugins/mqtt/mqtt-helpers.hpp b/plugins/mqtt/mqtt-helpers.hpp index d04a5409..d9c8e2e6 100644 --- a/plugins/mqtt/mqtt-helpers.hpp +++ b/plugins/mqtt/mqtt-helpers.hpp @@ -1,5 +1,6 @@ #pragma once #include "message-dispatcher.hpp" +#include "file-selection.hpp" #include "item-selection-helpers.hpp" #include "topic-selection.hpp" @@ -27,6 +28,9 @@ public: MqttConnection(const MqttConnection &other); MqttConnection(const std::string &name, const std::string &uri, const std::string &username, const std::string &password, + const std::string &trustStore, + const std::string &keyStore, + const std::string &privateKey, bool verifyServerCert, bool connectOnStart, bool reconnect, int reconnectDelay); static std::shared_ptr Create() { @@ -51,8 +55,12 @@ private: std::string _uri = "mqtt://localhost:1883"; std::string _username = "user"; std::string _password = "password"; + std::string _trustStore = ""; + std::string _keyStore = ""; + std::string _privateKey = ""; + bool _verifyServerCert = false; - std::vector _topics = {"/#"}; + std::vector _topics = {"/example"}; std::vector _qos = {1}; std::thread _thread; @@ -91,6 +99,10 @@ private: QLineEdit *_username; QLineEdit *_password; QPushButton *_showPassword; + FileSelection *_trustStore; + FileSelection *_keyStore; + FileSelection *_privateKey; + QCheckBox *_verifyServerCert; MqttTopicListWidget *_topics; QCheckBox *_connectOnStart; QCheckBox *_reconnect;