From 971ae6d1be24275a85bf383ccdbb68c88afb1a2c Mon Sep 17 00:00:00 2001 From: Martin Dummer Date: Wed, 12 Apr 2023 08:30:15 +0200 Subject: [PATCH] Feature: MQTT add TLS authentication User asked for TLS client certificate based login from DTU to MQTT server. This PR implements storage and use of x509 client certificate and private key. Signed-off-by: Martin Dummer --- include/Configuration.h | 3 +++ include/WebApi_mqtt.h | 2 +- include/defaults.h | 3 +++ src/Configuration.cpp | 6 ++++++ src/MqttSettings.cpp | 7 ++++++- src/WebApi_mqtt.cpp | 17 +++++++++++++++-- webapp/src/locales/de.json | 5 +++++ webapp/src/locales/en.json | 5 +++++ webapp/src/locales/fr.json | 5 +++++ webapp/src/types/MqttConfig.ts | 3 +++ webapp/src/types/MqttStatus.ts | 2 ++ webapp/src/views/MqttAdminView.vue | 15 +++++++++++++++ webapp/src/views/MqttInfoView.vue | 10 ++++++++++ 13 files changed, 79 insertions(+), 4 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index 2bdc9502..0620503a 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -90,6 +90,9 @@ struct CONFIG_T { bool Mqtt_Hass_IndividualPanels; bool Mqtt_Tls; char Mqtt_RootCaCert[MQTT_MAX_ROOT_CA_CERT_STRLEN + 1]; + bool Mqtt_TlsCertLogin; + char Mqtt_ClientCert[MQTT_MAX_ROOT_CA_CERT_STRLEN + 1]; + char Mqtt_ClientKey[MQTT_MAX_ROOT_CA_CERT_STRLEN + 1]; char Mqtt_Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1]; diff --git a/include/WebApi_mqtt.h b/include/WebApi_mqtt.h index 99a494f7..5eecc5bf 100644 --- a/include/WebApi_mqtt.h +++ b/include/WebApi_mqtt.h @@ -3,7 +3,7 @@ #include -#define MQTT_JSON_DOC_SIZE 3072 +#define MQTT_JSON_DOC_SIZE 10240 class WebApiMqttClass { public: diff --git a/include/defaults.h b/include/defaults.h index a2ba3168..8c371661 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -66,6 +66,9 @@ "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" \ "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" \ "-----END CERTIFICATE-----\n" +#define MQTT_TLSCERTLOGIN false +#define MQTT_TLSCLIENTCERT "" +#define MQTT_TLSCLIENTKEY "" #define MQTT_LWT_TOPIC "dtu/status" #define MQTT_LWT_ONLINE "online" #define MQTT_LWT_OFFLINE "offline" diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 12b184af..593d6dfe 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -65,6 +65,9 @@ bool ConfigurationClass::write() JsonObject mqtt_tls = mqtt.createNestedObject("tls"); mqtt_tls["enabled"] = config.Mqtt_Tls; mqtt_tls["root_ca_cert"] = config.Mqtt_RootCaCert; + mqtt_tls["certlogin"] = config.Mqtt_TlsCertLogin; + mqtt_tls["client_cert"] = config.Mqtt_ClientCert; + mqtt_tls["client_key"] = config.Mqtt_ClientKey; JsonObject mqtt_hass = mqtt.createNestedObject("hass"); mqtt_hass["enabled"] = config.Mqtt_Hass_Enabled; @@ -202,6 +205,9 @@ bool ConfigurationClass::read() JsonObject mqtt_tls = mqtt["tls"]; config.Mqtt_Tls = mqtt_tls["enabled"] | MQTT_TLS; strlcpy(config.Mqtt_RootCaCert, mqtt_tls["root_ca_cert"] | MQTT_ROOT_CA_CERT, sizeof(config.Mqtt_RootCaCert)); + config.Mqtt_TlsCertLogin = mqtt_tls["certlogin"] | MQTT_TLSCERTLOGIN; + strlcpy(config.Mqtt_ClientCert, mqtt_tls["client_cert"] | MQTT_TLSCLIENTCERT, sizeof(config.Mqtt_ClientCert)); + strlcpy(config.Mqtt_ClientKey, mqtt_tls["client_key"] | MQTT_TLSCLIENTKEY, sizeof(config.Mqtt_ClientKey)); JsonObject mqtt_hass = mqtt["hass"]; config.Mqtt_Hass_Enabled = mqtt_hass["enabled"] | MQTT_HASS_ENABLED; diff --git a/src/MqttSettings.cpp b/src/MqttSettings.cpp index 9548fbaa..da0363df 100644 --- a/src/MqttSettings.cpp +++ b/src/MqttSettings.cpp @@ -104,7 +104,12 @@ void MqttSettingsClass::performConnect() if (config.Mqtt_Tls) { static_cast(mqttClient)->setCACert(config.Mqtt_RootCaCert); static_cast(mqttClient)->setServer(config.Mqtt_Hostname, config.Mqtt_Port); - static_cast(mqttClient)->setCredentials(config.Mqtt_Username, config.Mqtt_Password); + if (config.Mqtt_TlsCertLogin) { + static_cast(mqttClient)->setCertificate(config.Mqtt_ClientCert); + static_cast(mqttClient)->setPrivateKey(config.Mqtt_ClientKey); + } else { + static_cast(mqttClient)->setCredentials(config.Mqtt_Username, config.Mqtt_Password); + } static_cast(mqttClient)->setWill(willTopic.c_str(), 2, config.Mqtt_Retain, config.Mqtt_LwtValue_Offline); static_cast(mqttClient)->setClientId(clientId.c_str()); static_cast(mqttClient)->onConnect(std::bind(&MqttSettingsClass::onMqttConnect, this, _1)); diff --git a/src/WebApi_mqtt.cpp b/src/WebApi_mqtt.cpp index 05fe2d13..ff40dadb 100644 --- a/src/WebApi_mqtt.cpp +++ b/src/WebApi_mqtt.cpp @@ -45,6 +45,8 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request) root["mqtt_retain"] = config.Mqtt_Retain; root["mqtt_tls"] = config.Mqtt_Tls; root["mqtt_root_ca_cert_info"] = getRootCaCertInfo(config.Mqtt_RootCaCert); + root["mqtt_tls_cert_login"] = config.Mqtt_TlsCertLogin; + root["mqtt_client_cert_info"] = getRootCaCertInfo(config.Mqtt_ClientCert); root["mqtt_lwt_topic"] = String(config.Mqtt_Topic) + config.Mqtt_LwtTopic; root["mqtt_publish_interval"] = config.Mqtt_PublishInterval; root["mqtt_hass_enabled"] = config.Mqtt_Hass_Enabled; @@ -76,6 +78,9 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request) root["mqtt_retain"] = config.Mqtt_Retain; root["mqtt_tls"] = config.Mqtt_Tls; root["mqtt_root_ca_cert"] = config.Mqtt_RootCaCert; + root["mqtt_tls_cert_login"] = config.Mqtt_TlsCertLogin; + root["mqtt_client_cert"] = config.Mqtt_ClientCert; + root["mqtt_client_key"] = config.Mqtt_ClientKey; root["mqtt_lwt_topic"] = config.Mqtt_LwtTopic; root["mqtt_lwt_online"] = config.Mqtt_LwtValue_Online; root["mqtt_lwt_offline"] = config.Mqtt_LwtValue_Offline; @@ -137,6 +142,9 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) && root.containsKey("mqtt_topic") && root.containsKey("mqtt_retain") && root.containsKey("mqtt_tls") + && root.containsKey("mqtt_tls_cert_login") + && root.containsKey("mqtt_client_cert") + && root.containsKey("mqtt_client_key") && root.containsKey("mqtt_lwt_topic") && root.containsKey("mqtt_lwt_online") && root.containsKey("mqtt_lwt_offline") @@ -212,8 +220,10 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) return; } - if (root["mqtt_root_ca_cert"].as().length() > MQTT_MAX_ROOT_CA_CERT_STRLEN) { - retMsg["message"] = "Certificate must not longer then " STR(MQTT_MAX_ROOT_CA_CERT_STRLEN) " characters!"; + if (root["mqtt_root_ca_cert"].as().length() > MQTT_MAX_ROOT_CA_CERT_STRLEN + || root["mqtt_client_cert"].as().length() > MQTT_MAX_ROOT_CA_CERT_STRLEN + || root["mqtt_client_key"].as().length() > MQTT_MAX_ROOT_CA_CERT_STRLEN) { + retMsg["message"] = "Certificates must not be longer than " STR(MQTT_MAX_ROOT_CA_CERT_STRLEN) " characters!"; retMsg["code"] = WebApiError::MqttCertificateLength; retMsg["param"]["max"] = MQTT_MAX_ROOT_CA_CERT_STRLEN; response->setLength(); @@ -291,6 +301,9 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) config.Mqtt_Retain = root["mqtt_retain"].as(); config.Mqtt_Tls = root["mqtt_tls"].as(); strlcpy(config.Mqtt_RootCaCert, root["mqtt_root_ca_cert"].as().c_str(), sizeof(config.Mqtt_RootCaCert)); + config.Mqtt_TlsCertLogin = root["mqtt_tls_cert_login"].as(); + strlcpy(config.Mqtt_ClientCert, root["mqtt_client_cert"].as().c_str(), sizeof(config.Mqtt_ClientCert)); + strlcpy(config.Mqtt_ClientKey, root["mqtt_client_key"].as().c_str(), sizeof(config.Mqtt_ClientKey)); config.Mqtt_Port = root["mqtt_port"].as(); strlcpy(config.Mqtt_Hostname, root["mqtt_hostname"].as().c_str(), sizeof(config.Mqtt_Hostname)); strlcpy(config.Mqtt_Username, root["mqtt_username"].as().c_str(), sizeof(config.Mqtt_Username)); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 0cc300e2..699f7747 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -259,6 +259,8 @@ "Retain": "Retain", "Tls": "TLS", "RootCertifcateInfo": "Root CA-Zertifikat-Informationen", + "TlsCertLogin": "Anmeldung mit TLS Zertifikat", + "ClientCertifcateInfo": "Client Zertifikat-Informationen", "HassSummary": "Home Assistant MQTT-Auto-Discovery Konfigurationszusammenfassung", "Expire": "Ablaufen", "IndividualPanels": "Einzelne Paneele", @@ -387,6 +389,9 @@ "EnableRetain": "Retain Flag aktivieren", "EnableTls": "TLS aktivieren", "RootCa": "CA-Root-Zertifikat (Standard Letsencrypt):", + "TlsCertLoginEnable": "TLS Zertifikat Login", + "ClientCert": "TLS Client-Zertifikat:", + "ClientKey": "TLS Client-Key:", "LwtParameters": "LWT-Parameter", "LwtTopic": "LWT-Topic:", "LwtTopicHint": "LWT-Topic, wird der Basis Topic angehängt", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 8cbd6099..527bcaff 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -259,6 +259,8 @@ "Retain": "Retain", "Tls": "TLS", "RootCertifcateInfo": "Root CA Certifcate Info", + "TlsCertLogin": "Login with TLS Certificate", + "ClientCertifcateInfo": "Client Certifcate Info", "HassSummary": "Home Assistant MQTT Auto Discovery Configuration Summary", "Expire": "Expire", "IndividualPanels": "Individual Panels", @@ -387,6 +389,9 @@ "EnableRetain": "Enable Retain Flag", "EnableTls": "Enable TLS", "RootCa": "CA-Root-Certificate (default Letsencrypt):", + "TlsCertLoginEnable": "Enable TLS Certificate Login", + "ClientCert": "TLS Client-Certificate:", + "ClientKey": "TLS Client-Key:", "LwtParameters": "LWT Parameters", "LwtTopic": "LWT Topic:", "LwtTopicHint": "LWT topic, will be append base topic", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index bbf79d87..d45ecaae 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -259,6 +259,8 @@ "Retain": "Conserver", "Tls": "TLS", "RootCertifcateInfo": "Informations sur le certificat de l'autorité de certification racine", + "TlsCertLogin": "Connexion avec un certificat TLS", + "ClientCertifcateInfo": "Informations sur le certificat du client", "HassSummary": "Résumé de la configuration de la découverte automatique du MQTT de Home Assistant", "Expire": "Expiration", "IndividualPanels": "Panneaux individuels", @@ -387,6 +389,9 @@ "EnableRetain": "Activation du maintien", "EnableTls": "Activer le TLS", "RootCa": "Certificat CA-Root (par défaut Letsencrypt)", + "TlsCertLoginEnable": "Activer la connexion par certificat TLS", + "ClientCert": "Certificat client TLS:", + "ClientKey": "Clé client TLS:", "LwtParameters": "Paramètres LWT", "LwtTopic": "Sujet LWT", "LwtTopicHint": "Sujet LWT, sera ajouté comme sujet de base", diff --git a/webapp/src/types/MqttConfig.ts b/webapp/src/types/MqttConfig.ts index 8077ae7b..dc6280ed 100644 --- a/webapp/src/types/MqttConfig.ts +++ b/webapp/src/types/MqttConfig.ts @@ -9,6 +9,9 @@ export interface MqttConfig { mqtt_retain: boolean; mqtt_tls: boolean; mqtt_root_ca_cert: string; + mqtt_tls_cert_login: boolean; + mqtt_client_cert: string; + mqtt_client_key: string; mqtt_lwt_topic: string; mqtt_lwt_online: string; mqtt_lwt_offline: string; diff --git a/webapp/src/types/MqttStatus.ts b/webapp/src/types/MqttStatus.ts index fe046edb..839d4854 100644 --- a/webapp/src/types/MqttStatus.ts +++ b/webapp/src/types/MqttStatus.ts @@ -8,6 +8,8 @@ export interface MqttStatus { mqtt_retain: boolean; mqtt_tls: boolean; mqtt_root_ca_cert_info: string; + mqtt_tls_cert_login: boolean; + mqtt_client_cert_info: string; mqtt_connected: boolean; mqtt_hass_enabled: boolean; mqtt_hass_expire: boolean; diff --git a/webapp/src/views/MqttAdminView.vue b/webapp/src/views/MqttAdminView.vue index 1c6cde92..6cd5ef24 100644 --- a/webapp/src/views/MqttAdminView.vue +++ b/webapp/src/views/MqttAdminView.vue @@ -60,6 +60,21 @@ :label="$t('mqttadmin.RootCa')" v-model="mqttConfigList.mqtt_root_ca_cert" type="textarea" maxlength="2560" rows="10"/> + + + + + + {{ $t('mqttinfo.RootCertifcateInfo') }} {{ mqttDataList.mqtt_root_ca_cert_info }} + + {{ $t('mqttinfo.TlsCertLogin') }} + + + + + + {{ $t('mqttinfo.ClientCertifcateInfo') }} + {{ mqttDataList.mqtt_client_cert_info }} +