Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MQTT add validate server cert parameter #3455

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions code/components/jomjol_flowcontroll/ClassFlowMQTT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ void ClassFlowMQTT::SetInitialParameter(void)
caCertFilename = "";
clientCertFilename = "";
clientKeyFilename = "";
validateServerCert = true;
clientname = wlan_config.hostname;

OldValue = "";
Expand Down Expand Up @@ -109,15 +110,19 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
std::string _param = GetParameterName(splitted[0]);
if ((toUpper(_param) == "CACERT") && (splitted.size() > 1))
{
this->caCertFilename = splitted[1];
this->caCertFilename = "/sdcard" + splitted[1];
}
if ((toUpper(_param) == "VALIDATESERVERCERT") && (splitted.size() > 1))
{
validateServerCert = alphanumericToBoolean(splitted[1]);
}
if ((toUpper(_param) == "CLIENTCERT") && (splitted.size() > 1))
{
this->clientCertFilename = splitted[1];
this->clientCertFilename = "/sdcard" + splitted[1];
}
if ((toUpper(_param) == "CLIENTKEY") && (splitted.size() > 1))
{
this->clientKeyFilename = splitted[1];
this->clientKeyFilename = "/sdcard" + splitted[1];
}
if ((toUpper(_param) == "USER") && (splitted.size() > 1))
{
Expand All @@ -133,10 +138,8 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
}
if ((toUpper(_param) == "RETAINMESSAGES") && (splitted.size() > 1))
{
if (toUpper(splitted[1]) == "TRUE") {
SetRetainFlag = true;
setMqtt_Server_Retain(SetRetainFlag);
}
SetRetainFlag = alphanumericToBoolean(splitted[1]);
setMqtt_Server_Retain(SetRetainFlag);
}
if ((toUpper(_param) == "HOMEASSISTANTDISCOVERY") && (splitted.size() > 1))
{
Expand Down Expand Up @@ -225,7 +228,7 @@ bool ClassFlowMQTT::Start(float AutoInterval)
mqttServer_setParameter(flowpostprocessing->GetNumbers(), keepAlive, roundInterval);

bool MQTTConfigCheck = MQTT_Configure(uri, clientname, user, password, maintopic, domoticzintopic, LWT_TOPIC, LWT_CONNECTED,
LWT_DISCONNECTED, caCertFilename, clientCertFilename, clientKeyFilename,
LWT_DISCONNECTED, caCertFilename, validateServerCert, clientCertFilename, clientKeyFilename,
keepAlive, SetRetainFlag, (void *)&GotConnected);

if (!MQTTConfigCheck) {
Expand Down Expand Up @@ -367,4 +370,4 @@ void ClassFlowMQTT::handleIdx(string _decsep, string _value)
}
}

#endif //ENABLE_MQTT
#endif //ENABLE_MQTT
3 changes: 2 additions & 1 deletion code/components/jomjol_flowcontroll/ClassFlowMQTT.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ class ClassFlowMQTT :
std::string OldValue;
ClassFlowPostProcessing* flowpostprocessing;
std::string user, password;
std::string caCertFilename, clientCertFilename, clientKeyFilename;
std::string caCertFilename, clientCertFilename, clientKeyFilename;
bool validateServerCert;
bool SetRetainFlag;
int keepAlive; // Seconds
float roundInterval; // Minutes
Expand Down
76 changes: 42 additions & 34 deletions code/components/jomjol_mqtt/interface_mqtt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
#include "esp_timer.h"
#endif


static const char *TAG = "MQTT IF";

std::map<std::string, std::function<void()>>* connectFunktionMap = NULL;
Expand All @@ -36,11 +35,11 @@ bool mqtt_connected = false;
esp_mqtt_client_handle_t client = NULL;
std::string uri, client_id, lwt_topic, lwt_connected, lwt_disconnected, user, password, maintopic, domoticz_in_topic;
std::string caCert, clientCert, clientKey;
bool validateServerCert = true;
int keepalive;
bool SetRetainFlag;
void (*callbackOnConnected)(std::string, bool) = NULL;


bool MQTTPublish(std::string _key, std::string _content, int qos, bool retained_flag)
{
if (!mqtt_enabled) { // MQTT sevice not started / configured (MQTT_Init not called before)
Expand Down Expand Up @@ -95,7 +94,6 @@ bool MQTTPublish(std::string _key, std::string _content, int qos, bool retained_
}
}


static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) {
std::string topic = "";
switch (event->event_id) {
Expand Down Expand Up @@ -197,16 +195,14 @@ static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) {
return ESP_OK;
}


static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, (int)event_id);
mqtt_event_handler_cb((esp_mqtt_event_handle_t) event_data);
}


bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password,
std::string _maintopic, std::string _domoticz_in_topic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected,
std::string _cacertfilename, std::string _clientcertfilename, std::string _clientkeyfilename,
std::string _cacertfilename, bool _validateServerCert, std::string _clientcertfilename, std::string _clientkeyfilename,
int _keepalive, bool _SetRetainFlag, void *_callbackOnConnected) {
if ((_mqttURI.length() == 0) || (_maintopic.length() == 0) || (_clientid.length() == 0))
{
Expand All @@ -225,25 +221,45 @@ bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _us
domoticz_in_topic = _domoticz_in_topic;
callbackOnConnected = ( void (*)(std::string, bool) )(_callbackOnConnected);

if (_clientcertfilename.length() && _clientkeyfilename.length()){
if (_clientcertfilename.length() && _clientkeyfilename.length()) {
std::ifstream cert_ifs(_clientcertfilename);
std::string cert_content((std::istreambuf_iterator<char>(cert_ifs)), (std::istreambuf_iterator<char>()));
clientCert = cert_content;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using clientCert: " + _clientcertfilename);
if (cert_ifs.is_open()) {
std::string cert_content((std::istreambuf_iterator<char>(cert_ifs)), (std::istreambuf_iterator<char>()));
clientCert = cert_content;
cert_ifs.close();
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using clientCert: " + _clientcertfilename);
}
else {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "could not open clientCert: " + _clientcertfilename);
}

std::ifstream key_ifs(_clientkeyfilename);
std::string key_content((std::istreambuf_iterator<char>(key_ifs)), (std::istreambuf_iterator<char>()));
clientKey = key_content;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using clientKey: " + _clientkeyfilename);
if (key_ifs.is_open()) {
std::string key_content((std::istreambuf_iterator<char>(key_ifs)), (std::istreambuf_iterator<char>()));
clientKey = key_content;
key_ifs.close();
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using clientKey: " + _clientkeyfilename);
}
else {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "could not open clientKey: " + _clientkeyfilename);
}
}

if (_cacertfilename.length() ){
std::ifstream ifs(_cacertfilename);
std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
caCert = content;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using caCert: " + _cacertfilename);
if (_cacertfilename.length()) {
std::ifstream ca_ifs(_cacertfilename);
if (ca_ifs.is_open()) {
std::string content((std::istreambuf_iterator<char>(ca_ifs)), (std::istreambuf_iterator<char>()));
caCert = content;
ca_ifs.close();
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using caCert: " + _cacertfilename);
}
else {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "could not open caCert: " + _cacertfilename);
}
}

validateServerCert = _validateServerCert;

if (_user.length() && _password.length()){
user = _user;
password = _password;
Expand All @@ -261,7 +277,6 @@ bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _us
return true;
}


int MQTT_Init() {
if (mqtt_initialized) {
return 0;
Expand Down Expand Up @@ -295,18 +310,21 @@ int MQTT_Init() {
mqtt_cfg.session.last_will.msg = lwt_disconnected.c_str();
mqtt_cfg.session.last_will.msg_len = (int)(lwt_disconnected.length());
mqtt_cfg.session.keepalive = keepalive;
mqtt_cfg.buffer.size = 1536; // size of MQTT send/receive buffer (Default: 1024)
mqtt_cfg.buffer.size = 2048; // size of MQTT send/receive buffer

if (caCert.length()){
if (caCert.length()) {
mqtt_cfg.broker.verification.certificate = caCert.c_str();
mqtt_cfg.broker.verification.certificate_len = caCert.length() + 1;
mqtt_cfg.broker.verification.skip_cert_common_name_check = true;

// Skip any validation of server certificate CN field, this reduces the
// security of TLS and makes the *MQTT* client susceptible to MITM attacks
mqtt_cfg.broker.verification.skip_cert_common_name_check = !validateServerCert;
}

if (clientCert.length() && clientKey.length()){
if (clientCert.length() && clientKey.length()) {
mqtt_cfg.credentials.authentication.certificate = clientCert.c_str();
mqtt_cfg.credentials.authentication.certificate_len = clientCert.length() + 1;

mqtt_cfg.credentials.authentication.key = clientKey.c_str();
mqtt_cfg.credentials.authentication.key_len = clientKey.length() + 1;
}
Expand Down Expand Up @@ -356,7 +374,6 @@ int MQTT_Init() {

}


void MQTTdestroy_client(bool _disable = false) {
if (client) {
if (mqtt_connected) {
Expand All @@ -374,17 +391,14 @@ void MQTTdestroy_client(bool _disable = false) {
mqtt_configOK = false;
}


bool getMQTTisEnabled() {
return mqtt_enabled;
}


bool getMQTTisConnected() {
return mqtt_connected;
}


bool mqtt_handler_flow_start(std::string _topic, char* _data, int _data_len)
{
ESP_LOGD(TAG, "Handler called: topic %s, data %.*s", _topic.c_str(), _data_len, _data);
Expand All @@ -393,7 +407,6 @@ bool mqtt_handler_flow_start(std::string _topic, char* _data, int _data_len)
return ESP_OK;
}


bool mqtt_handler_set_prevalue(std::string _topic, char* _data, int _data_len)
{
//ESP_LOGD(TAG, "Handler called: topic %s, data %.*s", _topic.c_str(), _data_len, _data);
Expand Down Expand Up @@ -429,7 +442,6 @@ bool mqtt_handler_set_prevalue(std::string _topic, char* _data, int _data_len)
return ESP_FAIL;
}


void MQTTconnected(){
if (mqtt_connected) {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Connected to broker");
Expand Down Expand Up @@ -464,7 +476,6 @@ void MQTTconnected(){
}
}


void MQTTregisterConnectFunction(std::string name, std::function<void()> func){
ESP_LOGD(TAG, "MQTTregisteronnectFunction %s\r\n", name.c_str());
if (connectFunktionMap == NULL) {
Expand All @@ -483,15 +494,13 @@ void MQTTregisterConnectFunction(std::string name, std::function<void()> func){
}
}


void MQTTunregisterConnectFunction(std::string name){
ESP_LOGD(TAG, "unregisterConnnectFunction %s\r\n", name.c_str());
if ((connectFunktionMap != NULL) && (connectFunktionMap->find(name) != connectFunktionMap->end())) {
connectFunktionMap->erase(name);
}
}


void MQTTregisterSubscribeFunction(std::string topic, std::function<bool(std::string, char*, int)> func){
ESP_LOGD(TAG, "registerSubscribeFunction %s", topic.c_str());
if (subscribeFunktionMap == NULL) {
Expand All @@ -506,7 +515,6 @@ void MQTTregisterSubscribeFunction(std::string topic, std::function<bool(std::st
(*subscribeFunktionMap)[topic] = func;
}


void MQTTdestroySubscribeFunction(){
if (subscribeFunktionMap != NULL) {
if (mqtt_connected) {
Expand Down
2 changes: 1 addition & 1 deletion code/components/jomjol_mqtt/interface_mqtt.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password,
std::string _maintopic, std::string _domoticz_in_topic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected,
std::string _cacertfilename, std::string _clientcertfilename, std::string _clientkeyfilename,
std::string _cacertfilename, bool _validateServerCert, std::string _clientcertfilename, std::string _clientkeyfilename,
int _keepalive, bool SetRetainFlag, void *callbackOnConnected);
int MQTT_Init();
void MQTTdestroy_client(bool _disable);
Expand Down
1 change: 1 addition & 0 deletions code/main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ extern "C" void app_main(void)
MakeDir("/sdcard/firmware"); // mandatory for OTA firmware update
MakeDir("/sdcard/img_tmp"); // mandatory for setting up alignment marks
MakeDir("/sdcard/demo"); // mandatory for demo mode
MakeDir("/sdcard/config/certs"); // mandatory for mqtt certificates

// Check for updates
// ********************************************
Expand Down
11 changes: 9 additions & 2 deletions code/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,15 @@ build_flags =
${common:esp32-idf.build_flags}
${flags:runtime.build_flags}
; ### Sofware options : (can be set in defines.h)
-D BOARD_ESP32CAM_AITHINKER
-D ENABLE_MQTT
-D BOARD_ESP32CAM_AITHINKER
-D ENABLE_MQTT
;-D MQTT_PROTOCOL_311
-D MQTT_ENABLE_SSL
;-D MQTT_ENABLE_WS
;-D MQTT_ENABLE_WSS
-D MQTT_SUPPORTED_FEATURE_SKIP_CRT_CMN_NAME_CHECK
;-D MQTT_SUPPORTED_FEATURE_CRT_CMN_NAME
;-D MQTT_SUPPORTED_FEATURE_CLIENT_KEY_PASSWORD
-D ENABLE_INFLUXDB
-D ENABLE_WEBHOOK
-D ENABLE_SOFTAP
Expand Down
6 changes: 6 additions & 0 deletions code/sdkconfig.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ CONFIG_MQTT_USE_CUSTOM_CONFIG=y
#CONFIG_MQTT_OUTBOX_EXPIRED_TIMEOUT_MS=5000
#CONFIG_MQTT_CUSTOM_OUTBOX=y # -> Use custom outbox in components/jomjol_mqtt/mqtt_outbox.h/cpp. If USE_PSRAM is enabled in there, it will save 10 kBytes of internal RAM. How ever it also leads to memory fragmentation, see https://github.com/jomjol/AI-on-the-edge-device/issues/2200

#
# mbedTLS
#
CONFIG_MBEDTLS_HAVE_TIME=y
CONFIG_MBEDTLS_HAVE_TIME_DATE=y

CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=n

CONFIG_CAMERA_CORE0=n
Expand Down
1 change: 1 addition & 0 deletions param-docs/expert-params.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,6 @@ Hostname
RSSIThreshold
TimeServer
CACert
ValidateServerCert
ClientCert
ClientKey
13 changes: 8 additions & 5 deletions param-docs/parameter-pages/MQTT/CACert.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
# Parameter `CACert`
Default Value: `""`

Example: `/config/certs/RootCA.pem`.
Example: `/config/certs/RootCA.crt`.

!!! Warning
This is an **Expert Parameter**! Only change it if you understand what it does!

Path to the CA certificate file.

This is part of the configuration to enable TLS for MQTT.
This is part of the configuration to enable TLS 1.2 for MQTT.<br>

The CA Certificate is used by the client to validate the broker is who it claims to be.
It allows the client to authenticate the server, which is the first part of the MTLS handshake.

Usually there is a common RootCA certificate for the MQTT broker
Usually there is a common RootCA certificate for the MQTT broker.
More information is available [here](https://jomjol.github.io/AI-on-the-edge-device-docs/MQTT-API/#mqtt-tls).

For more information on how to create your own certificate, see: [mosquitto.org](https://mosquitto.org/man/mosquitto-tls-7.html) or [emqx.com](https://www.emqx.com/en/blog/emqx-server-ssl-tls-secure-connection-configuration-guide).

!!! Note
This also means that you might have to change the protocol and port in [uri](https://jomjol.github.io/AI-on-the-edge-device-docs/Parameters/#parameter-uri) to `mqtts://example.com:8883`!

!!! Note
Only TLS 1.2 is supported!
Only Certificates up to 4096 Bit are supported!
15 changes: 8 additions & 7 deletions param-docs/parameter-pages/MQTT/ClientCert.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
# Parameter `ClientCert`
Default Value: `""`

Example: `/config/certs/client.pem.crt`.
Example: `/config/certs/client.crt`.

!!! Warning
This is an **Expert Parameter**! Only change it if you understand what it does!

Path to the Client Certificate file.

This is part of the configuration to enable TLS for MQTT.
This is part of the configuration to enable TLS 1.2 for MQTT.<br>

The Client Certificate is used by the client to prove its identity to the server, in conjunction with the Client Key.
It is the second part of the MTLS handshake.

Usually there is a one pair of Client Certificate/Key for each client that connects to the MQTT broker
Usually there is a one pair of Client Certificate/Key for each client that connects to the MQTT broker.
More information is available [here](https://jomjol.github.io/AI-on-the-edge-device-docs/MQTT-API/#mqtt-tls).

!!! Note
If set, `ClientKey` must be set too
This also means that you might have to change the protocol and port in [uri](https://jomjol.github.io/AI-on-the-edge-device-docs/Parameters/#parameter-uri) to `mqtts://example.com:8883`!
For more information on how to create your own certificate, see: [mosquitto.org](https://mosquitto.org/man/mosquitto-tls-7.html) or [emqx.com](https://www.emqx.com/en/blog/emqx-server-ssl-tls-secure-connection-configuration-guide).

!!! Note
Only TLS 1.2 is supported!
If set, `ClientKey` must be set too.
This also means that you might have to change the protocol and port in [uri](https://jomjol.github.io/AI-on-the-edge-device-docs/Parameters/#parameter-uri) to `mqtts://example.com:8883`!
Loading