Add SSL support to MQTT connections

Also fixes crash on startup if SSL was used while there was no support
for encrypted connections yet
This commit is contained in:
WarmUpTill 2025-10-22 11:55:26 +02:00 committed by WarmUpTill
parent e1164c4fa3
commit 84f7d0d214
4 changed files with 128 additions and 21 deletions

View File

@ -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..."

View File

@ -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:"

View File

@ -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<std::mutex> clientLock(_clientMtx);
_client = std::make_shared<mqtt::async_client>(
_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<std::mutex> clientLock(_clientMtx);
_client = std::make_shared<mqtt::async_client>(
_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;

View File

@ -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<Item> 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<std::string> _topics = {"/#"};
std::vector<std::string> _topics = {"/example"};
std::vector<int> _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;