From 464d1c155e001c340bd3e92c5215a3507da2de13 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 25 Sep 2021 21:22:10 +0200 Subject: [PATCH 001/100] [MQTT TLS] Add TLS support for MQTT --- docs/source/Controller/C016.rst | 2 +- docs/source/Controller/_Controller.rst | 63 +++++++++++++ platformio_esp82xx_envs.ini | 1 + src/_C002.ino | 3 + src/_C005.ino | 4 + src/_C006.ino | 4 + src/_C014.ino | 4 + .../DataStructs/ControllerSettingsStruct.cpp | 40 ++++++++ .../DataStructs/ControllerSettingsStruct.h | 8 +- src/src/DataStructs/ProtocolStruct.cpp | 11 ++- src/src/DataStructs/ProtocolStruct.h | 37 ++++---- src/src/DataTypes/TLS_types.cpp | 12 +++ src/src/DataTypes/TLS_types.h | 20 ++++ src/src/ESPEasyCore/Controller.cpp | 94 ++++++++++++++++++- src/src/Globals/MQTT.cpp | 17 +++- src/src/Globals/MQTT.h | 22 ++++- src/src/Helpers/_CPlugin_Helper_webform.cpp | 35 ++++++- src/src/WebServer/ControllerPage.cpp | 8 +- tools/pio/pre_custom_esp32.py | 1 + tools/pio/pre_custom_esp82xx.py | 1 + 20 files changed, 356 insertions(+), 31 deletions(-) create mode 100644 src/src/DataTypes/TLS_types.cpp create mode 100644 src/src/DataTypes/TLS_types.h diff --git a/docs/source/Controller/C016.rst b/docs/source/Controller/C016.rst index 53ee64861d..40eb6484cb 100644 --- a/docs/source/Controller/C016.rst +++ b/docs/source/Controller/C016.rst @@ -45,7 +45,7 @@ Each time a plugin sends data to this controller, a sample set is stored. A typical sample set contains: -- Timestamp (Default: Unix Time, but can be switched to "local time" in the controller settings) +- Timestamp (Default: Unix Time, but can be switched to "local time" in the controller settings with the "Use Local System Time" checkbox) - task index delivering the data - 4 float values diff --git a/docs/source/Controller/_Controller.rst b/docs/source/Controller/_Controller.rst index e990276c9d..ed2ee60359 100644 --- a/docs/source/Controller/_Controller.rst +++ b/docs/source/Controller/_Controller.rst @@ -74,6 +74,69 @@ before WiFi connection is made or during lost connection. For almost all controllers, sending data is a blocking call, so it may halt execution of other code on the node. With timouts longer than 2 seconds, the ESP may reboot as the software watchdog may step in. +TLS configuration +----------------- + +Added: 2021-09-26 + +Some protocols like MQTT may use TLS to provide a secure connection to the broker. + + +Still under development. +Notes: + +BearSSL::WiFiClientSecure net; + +Retrieve CA root certificate: +net.setCACert(local_root_ca); +BearSSL::X509List cert(digicert); +net.setTrustAnchors(&cert); + + +Retrieve public key of a specific certificate: ``openssl x509 -pubkey -noout -in ca.crt`` +BearSSL::PublicKey key(pubkey); +net.setKnownKey(&key); + + +Use certificate fingerprint (HEX checksum of certificate): +openssl x509 -fingerrint -in ca.crt + +net.setFingerprint(fp); + +Self Signed certificate Mosquitto: http://www.steves-internet-guide.com/mosquitto-tls/ +Let's encrypt Mosquitto: https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-the-mosquitto-mqtt-messaging-broker-on-ubuntu-18-04-quickstart + +See: https://www.youtube.com/watch?v=ytQUbyab4es + +https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt + +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- Sample ThingSpeak configuration diff --git a/platformio_esp82xx_envs.ini b/platformio_esp82xx_envs.ini index c52c850dbd..87654eaf8f 100644 --- a/platformio_esp82xx_envs.ini +++ b/platformio_esp82xx_envs.ini @@ -36,6 +36,7 @@ build_flags = ${regular_platform.build_flags} -DPLUGIN_BUILD_CUSTOM lib_ignore = ${esp8266_custom_common.lib_ignore} extra_scripts = ${esp8266_custom_common.extra_scripts} +board_build.f_cpu = 160000000L [env:custom_IR_ESP8266_4M1M] extends = esp8266_4M1M diff --git a/src/_C002.ino b/src/_C002.ino index 12888b832c..0e89a3fee5 100644 --- a/src/_C002.ino +++ b/src/_C002.ino @@ -38,6 +38,9 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& Protocol[protocolCount].usesExtCreds = true; Protocol[protocolCount].defaultPort = 1883; Protocol[protocolCount].usesID = true; + #ifdef USE_MQTT_TLS + Protocol[protocolCount].usesTLS = true; + #endif break; } diff --git a/src/_C005.ino b/src/_C005.ino index 0f50026784..e728d3fcc2 100644 --- a/src/_C005.ino +++ b/src/_C005.ino @@ -36,6 +36,10 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& Protocol[protocolCount].usesExtCreds = true; Protocol[protocolCount].defaultPort = 1883; Protocol[protocolCount].usesID = false; + #ifdef USE_MQTT_TLS + Protocol[protocolCount].usesTLS = true; + #endif + break; } diff --git a/src/_C006.ino b/src/_C006.ino index cdd7fd2256..a20df0b38c 100644 --- a/src/_C006.ino +++ b/src/_C006.ino @@ -37,6 +37,10 @@ bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String& Protocol[protocolCount].usesExtCreds = true; Protocol[protocolCount].defaultPort = 1883; Protocol[protocolCount].usesID = false; + #ifdef USE_MQTT_TLS + Protocol[protocolCount].usesTLS = true; + #endif + break; } diff --git a/src/_C014.ino b/src/_C014.ino index 939aae002b..2627733e79 100644 --- a/src/_C014.ino +++ b/src/_C014.ino @@ -181,6 +181,10 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& Protocol[protocolCount].usesExtCreds = true; Protocol[protocolCount].defaultPort = 1883; Protocol[protocolCount].usesID = false; + #ifdef USE_MQTT_TLS + Protocol[protocolCount].usesTLS = true; + #endif + break; } diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index 90844cece3..5fe05a4561 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -3,6 +3,7 @@ #include "../../ESPEasy_common.h" #include "../CustomBuild/ESPEasyLimits.h" +#include "../ESPEasyCore/ESPEasy_Log.h" #include "../ESPEasyCore/ESPEasyNetwork.h" #include "../Helpers/Misc.h" #include "../Helpers/Networking.h" @@ -14,6 +15,7 @@ #include #include + ControllerSettingsStruct::ControllerSettingsStruct() { reset(); @@ -75,6 +77,29 @@ void ControllerSettingsStruct::validate() { ZERO_TERMINATE(MQTTLwtTopic); ZERO_TERMINATE(LWTMessageConnect); ZERO_TERMINATE(LWTMessageDisconnect); + + #ifdef USES_MQTT + #ifdef USE_MQTT_TLS + if (TLStype() == TLS_types::NoTLS) { + if (Port == 8883) { + Port = 1883; + addLog(LOG_LEVEL_ERROR, F("Not using TLS, but port set to secure 8883. Use port 1883 instead")); + } + } else { + if (Port == 1883) { + Port = 8883; + addLog(LOG_LEVEL_ERROR, F("Using TLS, but port set to insecure port 1883. Use port 8883 instead")); + } + } + #else + if (Port == 8883) { + // No TLS support, so when switching builds, make sure it can still work. + Port = 1883; + addLog(LOG_LEVEL_ERROR, F("Not using TLS, but port set to secure 8883. Use port 1883 instead")); + } + #endif + #endif + } IPAddress ControllerSettingsStruct::getIP() const { @@ -286,3 +311,18 @@ void ControllerSettingsStruct::useLocalSystemTime(bool value) { bitWrite(VariousFlags, 11, value); } + +TLS_types ControllerSettingsStruct::TLStype() const +{ + // Store it in bits 12, 13, 14 + const TLS_types tls_type = static_cast((VariousFlags >> 12) & 0x7); + return tls_type; +} + +void ControllerSettingsStruct::TLStype(TLS_types tls_type) +{ + const uint32_t mask = ~(0x7); + VariousFlags &= mask; // Clear the bits + const uint32_t tls_type_val = static_cast(tls_type) << 12; + VariousFlags |= tls_type_val; +} diff --git a/src/src/DataStructs/ControllerSettingsStruct.h b/src/src/DataStructs/ControllerSettingsStruct.h index 6a87f97943..06ce6c3107 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.h +++ b/src/src/DataStructs/ControllerSettingsStruct.h @@ -9,6 +9,7 @@ #include // for std::nothrow #include "../../ESPEasy_common.h" +#include "../DataTypes/TLS_types.h" #include "../Globals/Plugins.h" class IPAddress; @@ -63,6 +64,7 @@ struct ControllerSettingsStruct CONTROLLER_HOSTNAME, CONTROLLER_IP, CONTROLLER_PORT, + CONTROLLER_MQTT_TLS_TYPE, CONTROLLER_USER, CONTROLLER_PASS, CONTROLLER_MIN_SEND_INTERVAL, @@ -145,10 +147,14 @@ struct ControllerSettingsStruct bool useLocalSystemTime() const; void useLocalSystemTime(bool value); + + + TLS_types TLStype() const; + void TLStype(TLS_types tls_type); boolean UseDNS; - uint8_t IP[4]; + uint8_t IP[4]; unsigned int Port; char HostName[65]; char Publish[129]; diff --git a/src/src/DataStructs/ProtocolStruct.cpp b/src/src/DataStructs/ProtocolStruct.cpp index 6306ce42f2..5fcc004c26 100644 --- a/src/src/DataStructs/ProtocolStruct.cpp +++ b/src/src/DataStructs/ProtocolStruct.cpp @@ -1,10 +1,11 @@ #include "ProtocolStruct.h" ProtocolStruct::ProtocolStruct() : - defaultPort(0), Number(0), usesMQTT(false), usesAccount(false), usesPassword(false), - usesTemplate(false), usesID(false), Custom(false), usesHost(true), usesPort(true), - usesQueue(true), usesCheckReply(true), usesTimeout(true), usesSampleSets(false), - usesExtCreds(false), needsNetwork(true), allowsExpire(true), allowLocalSystemTime(false) {} + defaultPort(0), Number(0), usesMQTT(false), usesAccount(false), usesPassword(false), + usesTemplate(false), usesID(false), Custom(false), usesHost(true), usesPort(true), + usesQueue(true), usesCheckReply(true), usesTimeout(true), usesSampleSets(false), + usesExtCreds(false), needsNetwork(true), allowsExpire(true), allowLocalSystemTime(false), + usesTLS(false) {} bool ProtocolStruct::useCredentials() const { return usesAccount || usesPassword; @@ -15,4 +16,4 @@ bool ProtocolStruct::useExtendedCredentials() const { return useCredentials(); } return false; -} \ No newline at end of file +} diff --git a/src/src/DataStructs/ProtocolStruct.h b/src/src/DataStructs/ProtocolStruct.h index e580f0b6b5..e8f7737471 100644 --- a/src/src/DataStructs/ProtocolStruct.h +++ b/src/src/DataStructs/ProtocolStruct.h @@ -16,23 +16,26 @@ struct ProtocolStruct bool useExtendedCredentials() const; uint16_t defaultPort; - uint8_t Number; - bool usesMQTT : 1; - bool usesAccount : 1; - bool usesPassword : 1; - bool usesTemplate : 1; // When set, the protocol will pre-load some templates like default MQTT topics - bool usesID : 1; // Whether a controller supports sending an IDX value sent along with plugin data - bool Custom : 1; // When set, the controller has to define all parameters on the controller setup page - bool usesHost : 1; - bool usesPort : 1; - bool usesQueue : 1; - bool usesCheckReply : 1; - bool usesTimeout : 1; - bool usesSampleSets : 1; - bool usesExtCreds : 1; - bool needsNetwork : 1; - bool allowsExpire : 1; - bool allowLocalSystemTime : 1; + uint8_t Number; + bool usesMQTT : 1; // Indicating whether it is a MQTT controller + bool usesAccount : 1; // Offer to enter credentials + bool usesPassword : 1; + bool usesTemplate : 1; // When set, the protocol will pre-load some templates like default MQTT topics + bool usesID : 1; // Whether a controller supports sending an IDX value sent along with plugin data + bool Custom : 1; // When set, the controller has to define all parameters on the controller setup page + bool usesHost : 1; // Offer either DNS hostname or IP + bool usesPort : 1; // Offer to set a port nr. This can be network port, but depending on controller this may be a + // different type of port. See LoRaWAN for example. + bool usesQueue : 1; // Allow to queue messages + bool usesCheckReply : 1; // Allow optional wait for reply + bool usesTimeout : 1; // Offer to set a timeout. + bool usesSampleSets : 1; // A sample set is an extra counter which is incremented as soon as a new value of set task is seen. + // (to keep track of bursts of messages where some may be lost) + bool usesExtCreds : 1; // Offer to store longer credentials + bool needsNetwork : 1; // Whether it needs a network connection to work + bool allowsExpire : 1; // Whether queued messages may be removed from the queue after some time + bool allowLocalSystemTime : 1; // Allow switching between Unix time and local time (including timezone and DST) + bool usesTLS : 1; // May offer TLS related settings and options }; typedef std::vector ProtocolVector; diff --git a/src/src/DataTypes/TLS_types.cpp b/src/src/DataTypes/TLS_types.cpp new file mode 100644 index 0000000000..5eaf38b4b1 --- /dev/null +++ b/src/src/DataTypes/TLS_types.cpp @@ -0,0 +1,12 @@ +#include "../DataTypes/TLS_types.h" + +const __FlashStringHelper* toString(TLS_types tls_type) +{ + switch (tls_type) { + case TLS_types::NoTLS: break; + case TLS_types::TLS_PSK: return F("TLS PreSharedKey"); + case TLS_types::TLS_CA_CERT: return F("TLS CA Cert"); + case TLS_types::TLS_insecure: return F("TLS No Checks (insecure)"); + } + return F("No TLS"); +} diff --git a/src/src/DataTypes/TLS_types.h b/src/src/DataTypes/TLS_types.h new file mode 100644 index 0000000000..8cf0e257d5 --- /dev/null +++ b/src/src/DataTypes/TLS_types.h @@ -0,0 +1,20 @@ +#ifndef DATATYPES_TLS_TYPES_H +#define DATATYPES_TLS_TYPES_H + + +#include +#include + +// Value is stored, so do not change assigned integer values. +enum class TLS_types { + NoTLS = 0, // Do not use encryption + TLS_PSK = 1, // Pre-Shared-Key + TLS_CA_CERT = 2, // Validate server certificate against known CA +//TLS_CA_CLI_CERT = 3, // TLS_CA_CERT + supply client certificate for authentication + TLS_insecure = 7 // Set as last option, do not check supplied certificate. Ideal for man-in-the-middle attack. +}; + +const __FlashStringHelper* toString(TLS_types tls_type); + + +#endif // ifndef DATATYPES_TLS_TYPES_H diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 6d712c15f0..faa6495a2a 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -173,7 +173,6 @@ bool MQTTConnect(controllerIndex_t controller_idx) { ++mqtt_reconnect_count; MakeControllerSettings(ControllerSettings); - if (!AllocatedControllerSettings()) { addLog(LOG_LEVEL_ERROR, F("MQTT : Cannot connect, out of RAM")); return false; @@ -192,13 +191,73 @@ bool MQTTConnect(controllerIndex_t controller_idx) // mqtt = WiFiClient(); // workaround see: https://github.com/esp8266/Arduino/issues/4497#issuecomment-373023864 delay(0); + + uint16_t mqttPort = ControllerSettings.Port; + +#ifdef USE_MQTT_TLS + mqtt_tls_last_errorstr = EMPTY_STRING; + mqtt_tls_last_error = 0; + const TLS_types TLS_type = ControllerSettings.TLStype(); + switch(TLS_type) { + case TLS_types::NoTLS: + { + mqtt.setTimeout(ControllerSettings.ClientTimeout); + MQTTclient.setClient(mqtt); + break; + } + case TLS_types::TLS_PSK: + { + //mqtt_tls.setPreSharedKey(const char *pskIdent, const char *psKey); // psKey in Hex + break; + } + case TLS_types::TLS_CA_CERT: + { + #ifdef ESP32 + mqtt_tls.setCACert(mqtt_rootCA); + #endif + #ifdef ESP8266 + mqtt_X509List.append(mqtt_rootCA); + mqtt_tls.setTrustAnchors(&mqtt_X509List); + #endif + break; + } + /* + case TLS_types::TLS_CA_CLI_CERT: + { + //mqtt_tls.setCertificate(const char *client_ca); + break; + } + */ + case TLS_types::TLS_insecure: + { + mqtt_tls.setInsecure(); + break; + } + } + if (TLS_type != TLS_types::NoTLS) { + mqtt_tls.setTimeout(ControllerSettings.ClientTimeout); + #ifdef ESP8266 + mqtt_tls.setBufferSizes(1024,1024); + #endif + MQTTclient.setClient(mqtt_tls); + if (mqttPort == 1883) { + mqttPort = 8883; + } + } else { + if (mqttPort == 8883) { + mqttPort = 1883; + } + } + +#else mqtt.setTimeout(ControllerSettings.ClientTimeout); MQTTclient.setClient(mqtt); +#endif if (ControllerSettings.UseDNS) { - MQTTclient.setServer(ControllerSettings.getHost().c_str(), ControllerSettings.Port); + MQTTclient.setServer(ControllerSettings.getHost().c_str(), mqttPort); } else { - MQTTclient.setServer(ControllerSettings.getIP(), ControllerSettings.Port); + MQTTclient.setServer(ControllerSettings.getIP(), mqttPort); } MQTTclient.setCallback(incoming_mqtt_callback); @@ -212,6 +271,8 @@ bool MQTTConnect(controllerIndex_t controller_idx) bool willRetain = ControllerSettings.mqtt_willRetain() && ControllerSettings.mqtt_sendLWT(); bool cleanSession = ControllerSettings.mqtt_cleanSession(); // As suggested here: + mqtt_last_connect_attempt.setNow(); + // https://github.com/knolleary/pubsubclient/issues/458#issuecomment-493875150 if (hasControllerCredentialsSet(controller_idx, ControllerSettings)) { @@ -240,8 +301,31 @@ bool MQTTConnect(controllerIndex_t controller_idx) uint8_t controller_number = Settings.Protocol[controller_idx]; count_connection_results(MQTTresult, F("MQTT : Broker "), controller_number); + #ifdef USE_MQTT_TLS + { + char buf[128] = {0}; + #ifdef ESP8266 + mqtt_tls_last_error = mqtt_tls.getLastSSLError(buf,128); + #endif + #ifdef ESP32 + mqtt_tls_last_error = mqtt_tls.lastError(buf,128); + #endif + mqtt_tls_last_errorstr = buf; + } + #endif + if (!MQTTresult) { + #ifdef USE_MQTT_TLS + if ((mqtt_tls_last_error != 0) && loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = F("MQTT : TLS error code: "); + log += mqtt_tls_last_error; + log += ' '; + log += mqtt_tls_last_errorstr; + addLog(LOG_LEVEL_ERROR, log); + } + #endif + MQTTclient.disconnect(); updateMQTTclient_connected(); return false; @@ -354,6 +438,10 @@ bool MQTTCheck(controllerIndex_t controller_idx) if (MQTTclient_should_reconnect || !MQTTclient.connected()) { + if (mqtt_last_connect_attempt.isSet() && mqtt_last_connect_attempt.millisPassedSince() < 5000) { + return false; + } + if (MQTTclient_should_reconnect) { addLog(LOG_LEVEL_ERROR, F("MQTT : Intentional reconnect")); } diff --git a/src/src/Globals/MQTT.cpp b/src/src/Globals/MQTT.cpp index e79a16c218..c5f5527ae0 100644 --- a/src/src/Globals/MQTT.cpp +++ b/src/src/Globals/MQTT.cpp @@ -3,12 +3,27 @@ #ifdef USES_MQTT // MQTT client -WiFiClient mqtt; +WiFiClient mqtt; +# ifdef USE_MQTT_TLS +String mqtt_tls_last_errorstr; +int32_t mqtt_tls_last_error = 0; + +# ifdef ESP32 +WiFiClientSecure mqtt_tls; +# endif // ifdef ESP32 +# ifdef ESP8266 +BearSSL::WiFiClientSecure mqtt_tls; +BearSSL::X509List mqtt_X509List; +# endif // ifdef ESP8266 +const char *mqtt_rootCA = nullptr; +# endif // ifdef USE_MQTT_TLS + PubSubClient MQTTclient(mqtt); bool MQTTclient_should_reconnect = true; bool MQTTclient_must_send_LWT_connected = false; bool MQTTclient_connected = false; int mqtt_reconnect_count = 0; +LongTermTimer mqtt_last_connect_attempt; #endif // USES_MQTT #ifdef USES_P037 diff --git a/src/src/Globals/MQTT.h b/src/src/Globals/MQTT.h index 69ef6b3104..af673b4e6f 100644 --- a/src/src/Globals/MQTT.h +++ b/src/src/Globals/MQTT.h @@ -5,17 +5,37 @@ #ifdef USES_MQTT +# include "../Helpers/LongTermTimer.h" # include # include +# ifdef USE_MQTT_TLS +# include +# endif // ifdef USE_MQTT_TLS + // MQTT client -extern WiFiClient mqtt; +extern WiFiClient mqtt; +# ifdef USE_MQTT_TLS +extern String mqtt_tls_last_errorstr; +extern int32_t mqtt_tls_last_error; +# ifdef ESP32 +extern WiFiClientSecure mqtt_tls; +# endif // ifdef ESP32 +# ifdef ESP8266 +extern BearSSL::WiFiClientSecure mqtt_tls; +extern BearSSL::X509List mqtt_X509List; + +# endif // ifdef ESP8266 + +extern const char *mqtt_rootCA; +# endif // ifdef USE_MQTT_TLS extern PubSubClient MQTTclient; extern bool MQTTclient_should_reconnect; extern bool MQTTclient_must_send_LWT_connected; extern bool MQTTclient_connected; extern int mqtt_reconnect_count; +extern LongTermTimer mqtt_last_connect_attempt; #endif // USES_MQTT #ifdef USES_P037 diff --git a/src/src/Helpers/_CPlugin_Helper_webform.cpp b/src/src/Helpers/_CPlugin_Helper_webform.cpp index 4e599bbc6b..1b80dbb22f 100644 --- a/src/src/Helpers/_CPlugin_Helper_webform.cpp +++ b/src/src/Helpers/_CPlugin_Helper_webform.cpp @@ -20,7 +20,8 @@ const __FlashStringHelper * toString(ControllerSettingsStruct::VarType parameter case ControllerSettingsStruct::CONTROLLER_USE_DNS: return F("Locate Controller"); case ControllerSettingsStruct::CONTROLLER_HOSTNAME: return F("Controller Hostname"); case ControllerSettingsStruct::CONTROLLER_IP: return F("Controller IP"); - case ControllerSettingsStruct::CONTROLLER_PORT: return F("Controller Port"); + case ControllerSettingsStruct::CONTROLLER_PORT: return F("Controller Port"); + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: return F("Use TLS"); case ControllerSettingsStruct::CONTROLLER_USER: return F("Controller User"); case ControllerSettingsStruct::CONTROLLER_PASS: return F("Controller Password"); @@ -152,6 +153,28 @@ void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettin addFormNumericBox(displayName, internalName, ControllerSettings.Port, 1, 65535); break; } + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: + { + #ifdef USE_MQTT_TLS + const int choice = static_cast(ControllerSettings.TLStype()); + #define NR_MQTT_TLS_TYPES 3 + const __FlashStringHelper * options[NR_MQTT_TLS_TYPES] = { + toString(TLS_types::NoTLS), +// toString(TLS_types::TLS_PSK), +// toString(TLS_types::TLS_CA_CERT), + toString(TLS_types::TLS_insecure) + }; + const int indices[NR_MQTT_TLS_TYPES] = { + static_cast(TLS_types::NoTLS), +// static_cast(TLS_types::TLS_PSK), +// static_cast(TLS_types::TLS_CA_CERT), + static_cast(TLS_types::TLS_insecure) + }; + addFormSelector(displayName, internalName, NR_MQTT_TLS_TYPES, options, indices, choice, true); + #undef NR_MQTT_TLS_TYPES + #endif + break; + } case ControllerSettingsStruct::CONTROLLER_USER: { const size_t fieldMaxLength = @@ -306,6 +329,16 @@ void saveControllerParameterForm(ControllerSettingsStruct & ControllerSet case ControllerSettingsStruct::CONTROLLER_PORT: ControllerSettings.Port = getFormItemInt(internalName, ControllerSettings.Port); break; + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: + { + #ifdef USE_MQTT_TLS + const int current = static_cast(ControllerSettings.TLStype()); + const TLS_types tls_type = static_cast(getFormItemInt(internalName, current)); + ControllerSettings.TLStype(tls_type); + #endif + break; + } + case ControllerSettingsStruct::CONTROLLER_USER: setControllerUser(controllerindex, ControllerSettings, webArg(internalName)); break; diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 1e2d206419..53627c63a8 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -135,6 +135,7 @@ void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSet ControllerSettings.reset(); ControllerSettings.Port = Protocol[ProtocolIndex].defaultPort; + ControllerSettings.TLStype(TLS_types::NoTLS); // Load some templates from the controller. struct EventStruct TempEvent; @@ -301,7 +302,6 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addHtmlError(F("Out of memory, cannot load page")); } else { LoadControllerSettings(controllerindex, ControllerSettings); - if (!Protocol[ProtocolIndex].Custom) { if (Protocol[ProtocolIndex].usesHost) { @@ -319,6 +319,12 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex if (Protocol[ProtocolIndex].usesPort) { addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PORT); } + #ifdef USES_MQTT + if (Protocol[ProtocolIndex].usesMQTT && Protocol[ProtocolIndex].usesTLS) { + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE); + addFormNote(F("Default ports: MQTT: 1883 / MQTT TLS: 8883")); + } + #endif if (Protocol[ProtocolIndex].usesQueue) { addTableSeparator(F("Controller Queue"), 2, 3); diff --git a/tools/pio/pre_custom_esp32.py b/tools/pio/pre_custom_esp32.py index 860a2e0fea..e0feb280f9 100644 --- a/tools/pio/pre_custom_esp32.py +++ b/tools/pio/pre_custom_esp32.py @@ -57,6 +57,7 @@ "-DFEATURE_SD", "-DFEATURE_I2CMULTIPLEXER", "-DUSE_TRIGONOMETRIC_FUNCTIONS_RULES", + "-DUSE_MQTT_TLS", "-DUSE_SETTINGS_ARCHIVE" ] diff --git a/tools/pio/pre_custom_esp82xx.py b/tools/pio/pre_custom_esp82xx.py index 200d4356b5..9c8de6a297 100644 --- a/tools/pio/pre_custom_esp82xx.py +++ b/tools/pio/pre_custom_esp82xx.py @@ -57,6 +57,7 @@ # "-DFEATURE_MDNS", # "-DFEATURE_SD", "-DUSE_EXT_RTC", + "-DUSE_MQTT_TLS", "-DFEATURE_I2CMULTIPLEXER", "-DUSE_TRIGONOMETRIC_FUNCTIONS_RULES", From 5fd64f217155efbc4034ed453451b043e56a8554 Mon Sep 17 00:00:00 2001 From: TD-er Date: Tue, 19 Oct 2021 16:02:40 +0200 Subject: [PATCH 002/100] [MQTT TLS] Add cert.py to extract certificate info --- platformio_core_defs.ini | 2 +- requirements.txt | 1 + tools/pio/cert.py | 127 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 tools/pio/cert.py diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index bedee8a98d..750355cb54 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -150,7 +150,7 @@ build_flags = -DESP32_STAGE [core_esp32_3_3_0] platform = espressif32 @ 3.3.0 platform_packages = framework-arduinoespressif32 -build_flags = -DESP32_STAGE +build_flags = [core_esp32_3_3_2] platform = espressif32 @ 3.3.2 diff --git a/requirements.txt b/requirements.txt index 4d9c717833..d33e574598 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ chardet==4.0.0 click==7.1.2 colorama==0.4.3 commonmark==0.9.1 +cryptography==35.0.0 docutils==0.16 idna==2.10 imagesize==1.2.0 diff --git a/tools/pio/cert.py b/tools/pio/cert.py new file mode 100644 index 0000000000..3ade0f5ccc --- /dev/null +++ b/tools/pio/cert.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 + +# Script to download/update certificates and public keys +# and generate compilable source files for c++/Arduino. +# released to public domain + +# Original: https://github.com/esp8266/Arduino/blob/master/tools/cert.py + +import urllib.request +import re +import ssl +import sys +import socket +import argparse +import datetime + +from cryptography import x509 +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.serialization import pkcs7 +from cryptography.hazmat.primitives.serialization import Encoding +from cryptography.hazmat.primitives.serialization import PublicFormat + +def printData(data, showPub = True): + try: + xcert = x509.load_der_x509_certificate(data) + except: + try: + xcert = x509.load_pem_x509_certificate(data) + except: + try: + xcert = pkcs7.load_der_pkcs7_certificates(data) + except: + xcert = pkcs7.load_pem_pkcs7_certificates(data) + if len(xcert) > 1: + print('// Warning: TODO: pkcs7 has {} entries'.format(len(xcert))) + xcert = xcert[0] + + cn = '' + for dn in xcert.subject.rfc4514_string().split(','): + keyval = dn.split('=') + if keyval[0] == 'CN': + cn += keyval[1] + name = re.sub('[^a-zA-Z0-9_]', '_', cn) + print('// CN: {} => name: {}'.format(cn, name)) + + print('// not valid before:', xcert.not_valid_before) + print('// not valid after: ', xcert.not_valid_after) + + if showPub: + + fingerprint = xcert.fingerprint(hashes.SHA1()).hex(':') + print('const char fingerprint_{} [] PROGMEM = "{}";'.format(name, fingerprint)) + + pem = xcert.public_key().public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo).decode('utf-8') + print('const char pubkey_{} [] PROGMEM = R"PUBKEY('.format(name)) + print(pem + ')PUBKEY";') + + else: + + cert = xcert.public_bytes(Encoding.PEM).decode('utf-8') + print('const char cert_{} [] PROGMEM = R"CERT('.format(name)) + print(cert + ')CERT";') + + cas = [] + for ext in xcert.extensions: + if ext.oid == x509.ObjectIdentifier("1.3.6.1.5.5.7.1.1"): + for desc in ext.value: + if desc.access_method == x509.oid.AuthorityInformationAccessOID.CA_ISSUERS: + cas.append(desc.access_location.value) + for ca in cas: + with urllib.request.urlopen(ca) as crt: + print() + print('// ' + ca) + printData(crt.read(), False) + print() + +def get_certificate(hostname, port, name): + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + with socket.create_connection((hostname, port)) as sock: + with context.wrap_socket(sock, server_hostname=hostname) as ssock: + print('////////////////////////////////////////////////////////////') + print('// certificate chain for {}:{}'.format(hostname, port)) + print() + if name: + print('const char* {}_host = "{}";'.format(name, hostname)); + print('const uint16_t {}_port = {};'.format(name, port)); + print() + printData(ssock.getpeercert(binary_form=True)) + print('// end of certificate chain for {}:{}'.format(hostname, port)) + print('////////////////////////////////////////////////////////////') + print() + return 0 + +def main(): + parser = argparse.ArgumentParser(description='download certificate chain and public keys under a C++/Arduino compilable form') + parser.add_argument('-s', '--server', action='store', required=True, help='TLS server dns name') + parser.add_argument('-p', '--port', action='store', required=False, help='TLS server port') + parser.add_argument('-n', '--name', action='store', required=False, help='variable name') + port = 443 + args = parser.parse_args() + server = args.server + port = 443 + try: + split = server.split(':') + server = split[0] + port = int(split[1]) + except: + pass + try: + port = int(args.port) + except: + pass + + print() + print('// this file is autogenerated - any modification will be overwritten') + print('// unused symbols will not be linked in the final binary') + print('// generated on {}'.format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) + print('// by {}'.format(sys.argv)) + print() + print('#pragma once') + print() + return get_certificate(server, port, args.name) + +if __name__ == '__main__': + sys.exit(main()) From 93b0c6db3ef6b7ba77b10f00010246b797f46ab4 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 01:32:14 +0100 Subject: [PATCH 003/100] [MQTT TLS] Allow to load CA root cert from file --- src/src/DataStructs/Caches.h | 8 +-- .../DataStructs/ControllerSettingsStruct.cpp | 33 +++++++++- .../DataStructs/ControllerSettingsStruct.h | 2 + src/src/DataTypes/TLS_types.h | 10 +-- src/src/ESPEasyCore/Controller.cpp | 29 ++++++-- src/src/Globals/MQTT.cpp | 2 +- src/src/Globals/MQTT.h | 2 +- src/src/Helpers/ESPEasy_Storage.cpp | 66 +++++++++++++++++-- src/src/Helpers/ESPEasy_Storage.h | 4 ++ src/src/Helpers/_CPlugin_Helper_webform.cpp | 18 ++++- src/src/WebServer/WebServer.cpp | 14 ++-- 11 files changed, 155 insertions(+), 33 deletions(-) diff --git a/src/src/DataStructs/Caches.h b/src/src/DataStructs/Caches.h index 4960c0436d..f1ebb331b1 100644 --- a/src/src/DataStructs/Caches.h +++ b/src/src/DataStructs/Caches.h @@ -5,9 +5,9 @@ #include "../../ESPEasy_common.h" #include "../Globals/Plugins.h" -typedef std::mapTaskIndexNameMap; -typedef std::map TaskIndexValueNameMap; -typedef std::map FilePresenceMap; +typedef std::map TaskIndexNameMap; +typedef std::map TaskIndexValueNameMap; +typedef std::map FilePresenceMap; struct Caches { void clearAllCaches(); @@ -18,7 +18,7 @@ struct Caches { TaskIndexNameMap taskIndexName; TaskIndexValueNameMap taskIndexValueName; - FilePresenceMap fileExistsMap; + FilePresenceMap fileExistsMap; // Filesize. -1 if not present bool activeTaskUseSerial0 = false; }; diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index 5fe05a4561..0a272123b1 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -314,15 +314,42 @@ void ControllerSettingsStruct::useLocalSystemTime(bool value) TLS_types ControllerSettingsStruct::TLStype() const { - // Store it in bits 12, 13, 14 - const TLS_types tls_type = static_cast((VariousFlags >> 12) & 0x7); + // Store it in bits 12, 13, 14, 15 + const TLS_types tls_type = static_cast((VariousFlags >> 12) & 0xF); return tls_type; } void ControllerSettingsStruct::TLStype(TLS_types tls_type) { - const uint32_t mask = ~(0x7); + const uint32_t mask = ~(0xF); VariousFlags &= mask; // Clear the bits const uint32_t tls_type_val = static_cast(tls_type) << 12; VariousFlags |= tls_type_val; } + +String ControllerSettingsStruct::getCertificateFilename() const +{ + String certFile = HostName; + if (certFile.isEmpty()) { + certFile = F(""); + } + + switch (TLStype()) { + case TLS_types::NoTLS: + case TLS_types::TLS_insecure: + return EMPTY_STRING; + case TLS_types::TLS_PSK: + certFile += F(".psk"); + break; + /* + case TLS_types::TLS_CA_CLI_CERT: + certFile += F(".caclicert"); + break; + */ + case TLS_types::TLS_CA_CERT: + certFile += F(".cacert"); + break; + } + + return certFile; +} \ No newline at end of file diff --git a/src/src/DataStructs/ControllerSettingsStruct.h b/src/src/DataStructs/ControllerSettingsStruct.h index eaa9f9376b..73aff6dc70 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.h +++ b/src/src/DataStructs/ControllerSettingsStruct.h @@ -151,6 +151,8 @@ struct ControllerSettingsStruct TLS_types TLStype() const; void TLStype(TLS_types tls_type); + + String getCertificateFilename() const; boolean UseDNS; diff --git a/src/src/DataTypes/TLS_types.h b/src/src/DataTypes/TLS_types.h index 8cf0e257d5..a8aec9aa14 100644 --- a/src/src/DataTypes/TLS_types.h +++ b/src/src/DataTypes/TLS_types.h @@ -7,11 +7,11 @@ // Value is stored, so do not change assigned integer values. enum class TLS_types { - NoTLS = 0, // Do not use encryption - TLS_PSK = 1, // Pre-Shared-Key - TLS_CA_CERT = 2, // Validate server certificate against known CA -//TLS_CA_CLI_CERT = 3, // TLS_CA_CERT + supply client certificate for authentication - TLS_insecure = 7 // Set as last option, do not check supplied certificate. Ideal for man-in-the-middle attack. + NoTLS = 0, // Do not use encryption + TLS_PSK = 1, // Pre-Shared-Key + TLS_CA_CERT = 2, // Validate server certificate against known CA +//TLS_CA_CLI_CERT = 3, // TLS_CA_CERT + supply client certificate for authentication + TLS_insecure = 0xF // Set as last option, do not check supplied certificate. Ideal for man-in-the-middle attack. }; const __FlashStringHelper* toString(TLS_types tls_type); diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 47dfd3ce3a..0866687986 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -213,13 +213,28 @@ bool MQTTConnect(controllerIndex_t controller_idx) } case TLS_types::TLS_CA_CERT: { - #ifdef ESP32 - mqtt_tls.setCACert(mqtt_rootCA); - #endif - #ifdef ESP8266 - mqtt_X509List.append(mqtt_rootCA); - mqtt_tls.setTrustAnchors(&mqtt_X509List); - #endif + const String certFile = ControllerSettings.getCertificateFilename(); + const size_t size = fileSize(certFile); + if (size > 0) { + if (mqtt_rootCA != nullptr) { + free(mqtt_rootCA); + } + mqtt_rootCA = (char*)malloc(size + 1); + if (mqtt_rootCA != nullptr) { + LoadFromFile(certFile.c_str(), 0, (uint8_t*)mqtt_rootCA, size); + mqtt_rootCA[size] = '\0'; + } + } + + if (mqtt_rootCA != nullptr) { + #ifdef ESP32 + mqtt_tls.setCACert(mqtt_rootCA); + #endif + #ifdef ESP8266 + mqtt_X509List.append(mqtt_rootCA); + mqtt_tls.setTrustAnchors(&mqtt_X509List); + #endif + } break; } /* diff --git a/src/src/Globals/MQTT.cpp b/src/src/Globals/MQTT.cpp index c5f5527ae0..ee9929fcf4 100644 --- a/src/src/Globals/MQTT.cpp +++ b/src/src/Globals/MQTT.cpp @@ -15,7 +15,7 @@ WiFiClientSecure mqtt_tls; BearSSL::WiFiClientSecure mqtt_tls; BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 -const char *mqtt_rootCA = nullptr; +char *mqtt_rootCA = nullptr; # endif // ifdef USE_MQTT_TLS PubSubClient MQTTclient(mqtt); diff --git a/src/src/Globals/MQTT.h b/src/src/Globals/MQTT.h index af673b4e6f..00b7ebd266 100644 --- a/src/src/Globals/MQTT.h +++ b/src/src/Globals/MQTT.h @@ -28,7 +28,7 @@ extern BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 -extern const char *mqtt_rootCA; +extern char *mqtt_rootCA; # endif // ifdef USE_MQTT_TLS extern PubSubClient MQTTclient; extern bool MQTTclient_should_reconnect; diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 5172d926a2..47cda56c13 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -118,14 +118,26 @@ String appendToFile(const String& fname, const uint8_t *data, unsigned int size) } bool fileExists(const String& fname) { + return (fileSize(fname) >= 0); +} + +int fileSize(const String& fname) { const String patched_fname = patch_fname(fname); auto search = Cache.fileExistsMap.find(patched_fname); if (search != Cache.fileExistsMap.end()) { - return search->second; + return search->second >= 0; + } + int size = -1; + if (ESPEASY_FS.exists(patched_fname)) { + size = 0; + fs::File f = ESPEASY_FS.open(patched_fname, "r"); + if (f) { + size = f.size(); + f.close(); + } } - bool res = ESPEASY_FS.exists(patched_fname); - Cache.fileExistsMap[patched_fname] = res; - return res; + Cache.fileExistsMap[patched_fname] = size; + return size; } fs::File tryOpenFile(const String& fname, const String& mode) { @@ -1237,6 +1249,52 @@ String LoadFromFile(const char *fname, int offset, uint8_t *memAddress, int data return String(); } +String LoadFromFile(const char *fname, String& data, int offset) +{ + fs::File f = tryOpenFile(fname, "r"); + SPIFFS_CHECK(f, fname); + #ifndef BUILD_NO_DEBUG + String log = F("LoadFromFile: "); + log += fname; + #else + String log = F("Load error"); + #endif + + if (!f || offset < 0 || (offset >= f.size())) { + #ifndef BUILD_NO_DEBUG + log += F(" ERROR, invalid position in file"); + #endif + addLog(LOG_LEVEL_ERROR, log); + return log; + } + delay(0); + START_TIMER; + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("LoadFromFile")); + #endif + + SPIFFS_CHECK(f.seek(offset, fs::SeekSet), fname); + if (f) { + if (!data.reserve(f.size() - offset)) { + #ifndef BUILD_NO_DEBUG + log += F(" ERROR, Out of memory"); + #endif + addLog(LOG_LEVEL_ERROR, log); + f.close(); + return log; + } + + while (f.available()) { data += (char)f.read(); } + f.close(); + } + + + STOP_TIMER(LOADFILE_STATS); + delay(0); + + return String(); +} + /********************************************************************************************\ Wrapper functions to handle errors in accessing settings \*********************************************************************************************/ diff --git a/src/src/Helpers/ESPEasy_Storage.h b/src/src/Helpers/ESPEasy_Storage.h index c9d88ceac6..c079c4350d 100644 --- a/src/src/Helpers/ESPEasy_Storage.h +++ b/src/src/Helpers/ESPEasy_Storage.h @@ -29,6 +29,8 @@ String appendToFile(const String& fname, const uint8_t *data, unsigned int size) bool fileExists(const String& fname); +int fileSize(const String& fname); + fs::File tryOpenFile(const String& fname, const String& mode); bool tryRenameFile(const String& fname_old, const String& fname_new); @@ -198,6 +200,8 @@ String ClearInFile(const char *fname, int index, int datasize); \*********************************************************************************************/ String LoadFromFile(const char *fname, int offset, uint8_t *memAddress, int datasize); +String LoadFromFile(const char *fname, String& data, int offset = 0); + /********************************************************************************************\ Wrapper functions to handle errors in accessing settings \*********************************************************************************************/ diff --git a/src/src/Helpers/_CPlugin_Helper_webform.cpp b/src/src/Helpers/_CPlugin_Helper_webform.cpp index f6a0c6ac09..7c89a385c8 100644 --- a/src/src/Helpers/_CPlugin_Helper_webform.cpp +++ b/src/src/Helpers/_CPlugin_Helper_webform.cpp @@ -161,17 +161,31 @@ void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettin const __FlashStringHelper * options[NR_MQTT_TLS_TYPES] = { toString(TLS_types::NoTLS), // toString(TLS_types::TLS_PSK), -// toString(TLS_types::TLS_CA_CERT), + toString(TLS_types::TLS_CA_CERT), toString(TLS_types::TLS_insecure) }; const int indices[NR_MQTT_TLS_TYPES] = { static_cast(TLS_types::NoTLS), // static_cast(TLS_types::TLS_PSK), -// static_cast(TLS_types::TLS_CA_CERT), + static_cast(TLS_types::TLS_CA_CERT), static_cast(TLS_types::TLS_insecure) }; addFormSelector(displayName, internalName, NR_MQTT_TLS_TYPES, options, indices, choice, true); #undef NR_MQTT_TLS_TYPES + const String certFile = ControllerSettings.getCertificateFilename(); + if (!certFile.isEmpty()) + { + const String certFile = ControllerSettings.getCertificateFilename(); + String note = F("Certificate or PSK must be stored on the filesystem in "); + note += certFile; + note += F(" "); + if (fileExists(certFile)) { + note += F("(File exists)"); + } else { + note += F("(Not found)"); + } + addFormNote(note); + } #endif break; } diff --git a/src/src/WebServer/WebServer.cpp b/src/src/WebServer/WebServer.cpp index e37debab8c..8191c26430 100644 --- a/src/src/WebServer/WebServer.cpp +++ b/src/src/WebServer/WebServer.cpp @@ -129,14 +129,16 @@ void sendHeadandTail(const String& tmplName, boolean Tail, boolean rebooting) { String fileName = tmplName; fileName += F(".htm"); - fs::File f = tryOpenFile(fileName, "r"); - if (f) { - pageTemplate.reserve(f.size()); + bool loadedFromFile = false; - while (f.available()) { pageTemplate += (char)f.read(); } - f.close(); - } else { + if (fileExists(fileName)) { + String res = LoadFromFile(fileName.c_str(), pageTemplate); + if (res.isEmpty()) { + loadedFromFile = true; + } + } + if (!loadedFromFile) { // TODO TD-er: Should send data directly to TXBuffer instead of using large strings. getWebPageTemplateDefault(tmplName, pageTemplate); } From 921c0f8fea49e43e3867df17beaa5fed8b078cd8 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 01:34:22 +0100 Subject: [PATCH 004/100] [Web] Allow to stream from file system (e.g. CSS inline) This may prevent additional calls to load the CSS from the file system in a separate HTTP GET call and also not loading the file into memory when streaming. --- src/src/Static/WebStaticData.cpp | 24 ++--- src/src/WebServer/404.cpp | 4 +- src/src/WebServer/CustomPage.cpp | 10 -- src/src/WebServer/LoadFromFS.cpp | 154 ++++++++++++++++++------------- src/src/WebServer/LoadFromFS.h | 6 +- src/src/WebServer/RootPage.cpp | 11 +-- 6 files changed, 113 insertions(+), 96 deletions(-) diff --git a/src/src/Static/WebStaticData.cpp b/src/src/Static/WebStaticData.cpp index 7c77168b47..3281ff1e0d 100644 --- a/src/src/Static/WebStaticData.cpp +++ b/src/src/Static/WebStaticData.cpp @@ -3,6 +3,7 @@ #include "../Globals/Cache.h" #include "../Helpers/ESPEasy_Storage.h" #include "../WebServer/HTML_wrappers.h" +#include "../WebServer/LoadFromFS.h" String generate_external_URL(const String& fname) { String url; @@ -28,13 +29,9 @@ void serve_CSS() { return; #endif } - - addHtml(F("'); + addHtml(F("")); } void serve_favicon() { @@ -112,10 +109,15 @@ void serve_JS(JSfiles_e JSfile) { html_add_script_end(); return; #endif + addHtml(F("'); + html_add_script_end(); + return; } - addHtml(F("'); + // Now stream the file directly from the file system. + html_add_script(false); + streamFromFS(url); html_add_script_end(); } \ No newline at end of file diff --git a/src/src/WebServer/404.cpp b/src/src/WebServer/404.cpp index 1edbfc006a..35883426ad 100644 --- a/src/src/WebServer/404.cpp +++ b/src/src/WebServer/404.cpp @@ -32,9 +32,7 @@ void handleNotFound() { if (handle_rules_edit(web_server.uri())) { return; } #endif - if (loadFromFS(true, web_server.uri())) { return; } - - if (loadFromFS(false, web_server.uri())) { return; } + if (loadFromFS(web_server.uri())) { return; } String message = F("URI: "); message += web_server.uri(); message += F("\nMethod: "); diff --git a/src/src/WebServer/CustomPage.cpp b/src/src/WebServer/CustomPage.cpp index 8135cb3f58..98ed4d4381 100644 --- a/src/src/WebServer/CustomPage.cpp +++ b/src/src/WebServer/CustomPage.cpp @@ -31,19 +31,9 @@ boolean handle_custom(String path) { if (!clientIPallowed()) { return false; } -#ifdef ESP8266 - // For ESP32 remove the leading slash - path = path.substring(1); -#endif - // create a dynamic custom page, parsing task values into [#] placeholders and parsing %xx% system variables fs::File dataFile = tryOpenFile(path.c_str(), "r"); -#ifdef ESP8266 const bool dashboardPage = path.startsWith(F("dashboard")); -#endif -#ifdef ESP32 - const bool dashboardPage = path.startsWith(F("/dashboard")); -#endif if (!dataFile && !dashboardPage) { return false; // unknown file that does not exist... diff --git a/src/src/WebServer/LoadFromFS.cpp b/src/src/WebServer/LoadFromFS.cpp index 6f9992a7c6..0a7c322496 100644 --- a/src/src/WebServer/LoadFromFS.cpp +++ b/src/src/WebServer/LoadFromFS.cpp @@ -1,46 +1,66 @@ #include "../WebServer/LoadFromFS.h" -#include "../WebServer/WebServer.h" -#include "../WebServer/CustomPage.h" #include "../Globals/RamTracker.h" + #include "../Helpers/ESPEasy_Storage.h" #include "../Helpers/Network.h" +#include "../WebServer/CustomPage.h" +#include "../WebServer/HTML_wrappers.h" +#include "../WebServer/WebServer.h" + #ifdef FEATURE_SD -#include -#endif +# include +#endif // ifdef FEATURE_SD -bool match_ext(const String& path, const __FlashStringHelper * ext) { - return (path.endsWith(ext) || path.endsWith(String(ext) + F(".gz"))); +bool match_ext(const String& path, const __FlashStringHelper *ext) { + return path.endsWith(ext) || path.endsWith(String(ext) + F(".gz")); } +bool gzipEncoded(const String& path) { + return path.endsWith(F(".gz")); +} -// ******************************************************************************** -// Web Interface server web file from FS -// ******************************************************************************** -bool loadFromFS(boolean spiffs, String path) { - // path is a deepcopy, since it will be changed here. - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("loadFromFS")); - #endif - - statusLED(true); - - String dataType = F("text/plain"); - bool mustCheckCredentials = false; - +String fileFromUrl(String path) { const int questionmarkPos = path.indexOf('?'); + if (questionmarkPos >= 0) { path = path.substring(0, questionmarkPos); } + // First prepend slash if (!path.startsWith(F("/"))) { path = String(F("/")) + path; } - if (path.endsWith(F("/"))) { path += F("index.htm"); } + #ifdef ESP8266 + // Remove leading slash to generate filename from it. + if (path.startsWith(F("/"))) { + path = path.substring(1); + } + #endif + + return path; +} + +// ******************************************************************************** +// Web Interface server web file from FS +// ******************************************************************************** +bool loadFromFS(String path) { + // path is a deepcopy, since it will be changed here. + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("loadFromFS")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + statusLED(true); + + String dataType = F("text/plain"); + bool mustCheckCredentials = false; + + path = fileFromUrl(path); + if (path.endsWith(F(".src"))) { path = path.substring(0, path.lastIndexOf(".")); } else if (match_ext(path, F(".htm")) || match_ext(path, F(".html"))) { dataType = F("text/html"); } else if (match_ext(path, F(".css"))) { dataType = F("text/css"); } @@ -52,15 +72,15 @@ bool loadFromFS(boolean spiffs, String path) { else if (path.endsWith(F(".svg"))) { dataType = F("image/svg+xml"); } else if (path.endsWith(F(".json"))) { dataType = F("application/json"); } else if (path.endsWith(F(".txt")) || - path.endsWith(F(".dat"))) { + path.endsWith(F(".dat"))) { mustCheckCredentials = true; - dataType = F("application/octet-stream"); + dataType = F("application/octet-stream"); } #ifdef WEBSERVER_CUSTOM else if (path.endsWith(F(".esp"))) { - return handle_custom(path); + return handle_custom(path); } -#endif +#endif // ifdef WEBSERVER_CUSTOM else { mustCheckCredentials = true; } @@ -78,53 +98,63 @@ bool loadFromFS(boolean spiffs, String path) { } #endif // ifndef BUILD_NO_DEBUG -#if !defined(ESP32) - path = path.substring(1); -#endif // if !defined(ESP32) + fs::File f; - if (spiffs) - { - if (!fileExists(path)) { - return false; - } - fs::File dataFile = tryOpenFile(path.c_str(), "r"); - - if (!dataFile) { - return false; - } + // Search flash file system first, then SD if present + f = tryOpenFile(path.c_str(), "r"); + #ifdef FEATURE_SD + if (!f) { + f = SD.open(path.c_str(), "r"); + } + #endif // ifdef FEATURE_SD - // prevent reloading stuff on every click - web_server.sendHeader(F("Cache-Control"), F("max-age=3600, public")); - web_server.sendHeader(F("Vary"), "*"); - web_server.sendHeader(F("ETag"), F("\"2.0.0\"")); + if (!f) { + return false; + } - if (path.endsWith(F(".dat"))) { - web_server.sendHeader(F("Content-Disposition"), F("attachment;")); - } + // prevent reloading stuff on every click + web_server.sendHeader(F("Cache-Control"), F("max-age=3600, public")); + web_server.sendHeader(F("Vary"), "*"); + web_server.sendHeader(F("ETag"), F("\"2.0.0\"")); - web_server.streamFile(dataFile, dataType); - dataFile.close(); + if (path.endsWith(F(".dat"))) { + web_server.sendHeader(F("Content-Disposition"), F("attachment;")); } - else - { -#ifdef FEATURE_SD - File dataFile = SD.open(path.c_str()); + if (gzipEncoded(path)) { + web_server.sendHeader(F("Content-Encoding"), F("gzip")); + } + + web_server.streamFile(f, dataType); + f.close(); + + statusLED(true); + return true; +} + +bool streamFromFS(String path) { + // path is a deepcopy, since it will be changed here. + path = fileFromUrl(path); + statusLED(true); - if (!dataFile) { - return false; - } + fs::File f; - if (path.endsWith(F(".DAT"))) { - web_server.sendHeader(F("Content-Disposition"), F("attachment;")); - } - web_server.streamFile(dataFile, dataType); - dataFile.close(); -#else // ifdef FEATURE_SD + // Search flash file system first, then SD if present + f = tryOpenFile(path.c_str(), "r"); + #ifdef FEATURE_SD + if (!f) { + f = SD.open(path.c_str(), "r"); + } + #endif // ifdef FEATURE_SD - // File from SD requested, but no SD support. + if (!f) { return false; -#endif // ifdef FEATURE_SD + } + + while (f.available()) { + addHtml((char)f.read()); } statusLED(true); + + f.close(); return true; } diff --git a/src/src/WebServer/LoadFromFS.h b/src/src/WebServer/LoadFromFS.h index de51704aa5..352ad980ad 100644 --- a/src/src/WebServer/LoadFromFS.h +++ b/src/src/WebServer/LoadFromFS.h @@ -4,6 +4,10 @@ #include "../WebServer/common.h" -bool loadFromFS(boolean spiffs, String path); +bool loadFromFS(String path); + + +// Send the content of a file directly to the webserver, like addHtml() +bool streamFromFS(String path); #endif \ No newline at end of file diff --git a/src/src/WebServer/RootPage.cpp b/src/src/WebServer/RootPage.cpp index f939d0fbaa..dac0c3d11c 100644 --- a/src/src/WebServer/RootPage.cpp +++ b/src/src/WebServer/RootPage.cpp @@ -81,15 +81,8 @@ void handle_root() { navMenuIndex = 0; // if index.htm exists on FS serve that one (first check if gziped version exists) - if (loadFromFS(true, F("/index.htm.gz"))) { return; } - #ifdef FEATURE_SD - if (loadFromFS(false, F("/index.htm.gz"))) { return; } - #endif - - if (loadFromFS(true, F("/index.htm"))) { return; } - #ifdef FEATURE_SD - if (loadFromFS(false, F("/index.htm"))) { return; } - #endif + if (loadFromFS(F("/index.htm.gz"))) { return; } + if (loadFromFS(F("/index.htm"))) { return; } TXBuffer.startStream(); From 596a73987913bbf9673af75185da75c0f99a4b44 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 01:36:21 +0100 Subject: [PATCH 005/100] [MQTT TLS] Patch CA root cert to match strict layout Making it easier for users to copy/paste certificate code into a file. It will be patched at load from the file system. --- src/src/Helpers/ESPEasy_Storage.cpp | 131 +++++++++++++++++++++++++++- src/src/Helpers/ESPEasy_Storage.h | 7 ++ 2 files changed, 137 insertions(+), 1 deletion(-) diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 47cda56c13..874ddfdeda 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -1012,6 +1012,135 @@ String LoadNotificationSettings(int NotificationIndex, uint8_t *memAddress, int return LoadFromFile(SettingsType::Enum::NotificationSettings_Type, NotificationIndex, memAddress, datasize); } + +/********************************************************************************************\ + Handle certificate files on the file system. + The content will be stripped from unusable character like quotes, spaces etc. + \*********************************************************************************************/ +static inline bool is_base64(char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +bool cleanupCertificate(String & certificate, bool &changed) +{ + changed = false; + // "-----BEGIN CERTIFICATE-----" positions in dash_pos[0] and dash_pos[1] + // "-----END CERTIFICATE-----" positions in dash_pos[2] and dash_pos[3] + int dash_pos[4] = { 0 }; + int last_pos = 0; + for (int i = 0; i < 4 && last_pos != -1; ++i) { + dash_pos[i] = certificate.indexOf(F("-----"), last_pos); + last_pos = dash_pos[i] + 5; + addLog(LOG_LEVEL_INFO, String(F(" dash_pos: ")) + String(dash_pos[i])); + } + if (last_pos == -1) return false; + + int read_pos = dash_pos[1] + 5; // next char after "-----BEGIN CERTIFICATE-----" + String newCert; + newCert.reserve((dash_pos[3] + 6) - dash_pos[0]); + + // "-----BEGIN CERTIFICATE-----" + newCert += certificate.substring(dash_pos[0], read_pos); + + char last_char = certificate[read_pos - 1]; + for (; read_pos < dash_pos[2]; ++read_pos) { + const char c = certificate[read_pos]; + if ((c == 'n' && last_char == '\\') || (c == '\n')) { + if (!newCert.endsWith(String('\n'))) { + newCert += '\n'; + } + } else if (is_base64(c) || c == '=') { + newCert += c; + } + last_char = c; + } + + // "-----END CERTIFICATE-----" + newCert += certificate.substring(dash_pos[2], dash_pos[3] + 5); + newCert += '\n'; + + changed = !certificate.equals(newCert); + certificate = std::move(newCert); + return true; +} + + +String SaveCertificate(const String& fname, const String& certificate) +{ + return SaveToFile(fname.c_str(), 0, (const uint8_t *)certificate.c_str(), certificate.length() + 1); +} + +String LoadCertificate(const String& fname, String& certificate) +{ + bool changed = false; + if (fileExists(fname)) { + fs::File f = tryOpenFile(fname, "r"); + SPIFFS_CHECK(f, fname.c_str()); + #ifndef BUILD_NO_DEBUG + String log = F("LoadCertificate: "); + log += fname; + #else + String log = F("LoadCertificate error"); + #endif + + certificate.clear(); + + if (!certificate.reserve(f.size())) { + #ifndef BUILD_NO_DEBUG + log += F(" ERROR, Out of memory"); + #endif + addLog(LOG_LEVEL_ERROR, log); + f.close(); + return log; + } + bool done = false; + while (f.available() && !done) { + const char c = (char)f.read(); + if (c == '\0') { + done = true; + } else { + certificate += c; + } + } + f.close(); + + String analyse = F("Cleanup: Before: "); + analyse += certificate.length(); + analyse += F(" After: "); + + if (!cleanupCertificate(certificate, changed)) { + certificate.clear(); + #ifndef BUILD_NO_DEBUG + log += F(" ERROR, Invalid certificate format"); + #endif + addLog(LOG_LEVEL_ERROR, log); + return log; + } else if (changed) { + //return SaveCertificate(fname, certificate); + } +// addLog(LOG_LEVEL_INFO, F("After")); +// addLog(LOG_LEVEL_INFO, certificate); + static int previousFree = FreeMem(); + const int freemem = FreeMem(); + + analyse += certificate.length(); + analyse += changed ? F(" changed") : F(" same"); + analyse += F(" free memory: "); + analyse += freemem; + analyse += F(" largest free block: "); + analyse += getMaxFreeBlock(); + + analyse += F(" Difference: "); + analyse += previousFree - freemem; + + addLog(LOG_LEVEL_INFO, analyse); + previousFree = freemem; + } + + return EMPTY_STRING; +} + + /********************************************************************************************\ Init a file with zeros on file system \*********************************************************************************************/ @@ -1260,7 +1389,7 @@ String LoadFromFile(const char *fname, String& data, int offset) String log = F("Load error"); #endif - if (!f || offset < 0 || (offset >= f.size())) { + if (!f || offset < 0 || (offset >= static_cast(f.size()))) { #ifndef BUILD_NO_DEBUG log += F(" ERROR, invalid position in file"); #endif diff --git a/src/src/Helpers/ESPEasy_Storage.h b/src/src/Helpers/ESPEasy_Storage.h index c079c4350d..620c041bbe 100644 --- a/src/src/Helpers/ESPEasy_Storage.h +++ b/src/src/Helpers/ESPEasy_Storage.h @@ -172,6 +172,13 @@ String SaveNotificationSettings(int NotificationIndex, const uint8_t *memAddress \*********************************************************************************************/ String LoadNotificationSettings(int NotificationIndex, uint8_t *memAddress, int datasize); +/********************************************************************************************\ + Handle certificate files on the file system. + The content will be stripped from unusable character like quotes, spaces etc. + \*********************************************************************************************/ +String SaveCertificate(const String& fname, const String& certificate); +String LoadCertificate(const String& fname, String& certificate); + /********************************************************************************************\ Init a file with zeros on file system From e006c19f569b05b6dab93ac5a124fbbd9929da91 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 01:38:35 +0100 Subject: [PATCH 006/100] [MQTT TLS] Work-around for memory leak in MbedTLS (not finished) There is a memory leak in Mbed TLS, when connection failed. For example when using a CA root certificate which does not match the certificate of the host we're connecting to. This will take about 1880 bytes of memory on each attempt. Still a work-in-progress as it is not yet fixed. --- src/src/ESPEasyCore/Controller.cpp | 80 ++- src/src/Globals/MQTT.cpp | 6 +- src/src/Globals/MQTT.h | 10 +- src/src/Helpers/ESPEasy_WiFiClientSecure.cpp | 341 ++++++++++++ src/src/Helpers/ESPEasy_WiFiClientSecure.h | 113 ++++ src/src/Helpers/ESPEasy_ssl_client.cpp | 530 +++++++++++++++++++ src/src/Helpers/ESPEasy_ssl_client.h | 53 ++ 7 files changed, 1104 insertions(+), 29 deletions(-) create mode 100644 src/src/Helpers/ESPEasy_WiFiClientSecure.cpp create mode 100644 src/src/Helpers/ESPEasy_WiFiClientSecure.h create mode 100644 src/src/Helpers/ESPEasy_ssl_client.cpp create mode 100644 src/src/Helpers/ESPEasy_ssl_client.h diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 0866687986..ed30513d43 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -25,6 +25,7 @@ #include "../Globals/Protocol.h" #include "../Helpers/_CPlugin_Helper.h" +#include "../Helpers/Memory.h" #include "../Helpers/Misc.h" #include "../Helpers/Network.h" #include "../Helpers/PeriodicalActions.h" @@ -186,6 +187,14 @@ bool MQTTConnect(controllerIndex_t controller_idx) if (MQTTclient.connected()) { MQTTclient.disconnect(); + # ifdef USE_MQTT_TLS + /* + if (mqtt_tls != nullptr) { + delete mqtt_tls; + mqtt_tls = nullptr; + } + */ + #endif } updateMQTTclient_connected(); @@ -199,6 +208,19 @@ bool MQTTConnect(controllerIndex_t controller_idx) mqtt_tls_last_errorstr = EMPTY_STRING; mqtt_tls_last_error = 0; const TLS_types TLS_type = ControllerSettings.TLStype(); + if (TLS_type != TLS_types::NoTLS) { + #ifdef ESP32 + mqtt_tls = new ESPEasy_WiFiClientSecure; + #endif + #ifdef ESP8266 + mqtt_tls = new BearSSL::WiFiClientSecure; + #endif + + if (mqtt_tls == nullptr) { + addLog(LOG_LEVEL_ERROR, F("MQTT : Could not create TLS client, out of memory")); + return false; + } + } switch(TLS_type) { case TLS_types::NoTLS: { @@ -208,31 +230,39 @@ bool MQTTConnect(controllerIndex_t controller_idx) } case TLS_types::TLS_PSK: { - //mqtt_tls.setPreSharedKey(const char *pskIdent, const char *psKey); // psKey in Hex + //mqtt_tls->setPreSharedKey(const char *pskIdent, const char *psKey); // psKey in Hex break; } case TLS_types::TLS_CA_CERT: { - const String certFile = ControllerSettings.getCertificateFilename(); - const size_t size = fileSize(certFile); - if (size > 0) { - if (mqtt_rootCA != nullptr) { - free(mqtt_rootCA); - } - mqtt_rootCA = (char*)malloc(size + 1); - if (mqtt_rootCA != nullptr) { - LoadFromFile(certFile.c_str(), 0, (uint8_t*)mqtt_rootCA, size); - mqtt_rootCA[size] = '\0'; - } +// mqtt_rootCA.clear(); + if (mqtt_rootCA.isEmpty()) + LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_rootCA); + + { + static int previousFree = FreeMem(); + const int freemem = FreeMem(); + + String analyse = F(" free memory: "); + analyse += freemem; + analyse += F(" largest free block: "); + analyse += getMaxFreeBlock(); + + analyse += F(" Difference: "); + analyse += previousFree - freemem; + + addLog(LOG_LEVEL_INFO, analyse); + previousFree = freemem; } - if (mqtt_rootCA != nullptr) { + + if (mqtt_rootCA.length() > 0) { #ifdef ESP32 - mqtt_tls.setCACert(mqtt_rootCA); + mqtt_tls->setCACert(mqtt_rootCA.c_str()); #endif #ifdef ESP8266 - mqtt_X509List.append(mqtt_rootCA); - mqtt_tls.setTrustAnchors(&mqtt_X509List); + mqtt_X509List.append(mqtt_rootCA.c_str()); + mqtt_tls->setTrustAnchors(&mqtt_X509List); #endif } break; @@ -240,22 +270,22 @@ bool MQTTConnect(controllerIndex_t controller_idx) /* case TLS_types::TLS_CA_CLI_CERT: { - //mqtt_tls.setCertificate(const char *client_ca); + //mqtt_tls->setCertificate(const char *client_ca); break; } */ case TLS_types::TLS_insecure: { - mqtt_tls.setInsecure(); + mqtt_tls->setInsecure(); break; } } if (TLS_type != TLS_types::NoTLS) { - mqtt_tls.setTimeout(ControllerSettings.ClientTimeout); + mqtt_tls->setTimeout(ControllerSettings.ClientTimeout); #ifdef ESP8266 - mqtt_tls.setBufferSizes(1024,1024); + mqtt_tls->setBufferSizes(1024,1024); #endif - MQTTclient.setClient(mqtt_tls); + MQTTclient.setClient(*mqtt_tls); if (mqttPort == 1883) { mqttPort = 8883; } @@ -321,10 +351,10 @@ bool MQTTConnect(controllerIndex_t controller_idx) { char buf[128] = {0}; #ifdef ESP8266 - mqtt_tls_last_error = mqtt_tls.getLastSSLError(buf,128); + mqtt_tls_last_error = mqtt_tls->getLastSSLError(buf,128); #endif #ifdef ESP32 - mqtt_tls_last_error = mqtt_tls.lastError(buf,128); + mqtt_tls_last_error = mqtt_tls->lastError(buf,128); #endif mqtt_tls_last_errorstr = buf; } @@ -343,6 +373,10 @@ bool MQTTConnect(controllerIndex_t controller_idx) #endif MQTTclient.disconnect(); + #ifdef USE_MQTT_TLS + mqtt_tls->stop(); + #endif + updateMQTTclient_connected(); return false; } diff --git a/src/src/Globals/MQTT.cpp b/src/src/Globals/MQTT.cpp index ee9929fcf4..d859f7b138 100644 --- a/src/src/Globals/MQTT.cpp +++ b/src/src/Globals/MQTT.cpp @@ -9,13 +9,13 @@ String mqtt_tls_last_errorstr; int32_t mqtt_tls_last_error = 0; # ifdef ESP32 -WiFiClientSecure mqtt_tls; +ESPEasy_WiFiClientSecure* mqtt_tls; # endif // ifdef ESP32 # ifdef ESP8266 -BearSSL::WiFiClientSecure mqtt_tls; +BearSSL::WiFiClientSecure* mqtt_tls; BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 -char *mqtt_rootCA = nullptr; +String mqtt_rootCA; # endif // ifdef USE_MQTT_TLS PubSubClient MQTTclient(mqtt); diff --git a/src/src/Globals/MQTT.h b/src/src/Globals/MQTT.h index 00b7ebd266..3cf7ff14b3 100644 --- a/src/src/Globals/MQTT.h +++ b/src/src/Globals/MQTT.h @@ -11,7 +11,11 @@ # include # ifdef USE_MQTT_TLS +# ifdef ESP32 +# include "../Helpers/ESPEasy_WiFiClientSecure.h" +# else # include +# endif # endif // ifdef USE_MQTT_TLS // MQTT client @@ -20,15 +24,15 @@ extern WiFiClient mqtt; extern String mqtt_tls_last_errorstr; extern int32_t mqtt_tls_last_error; # ifdef ESP32 -extern WiFiClientSecure mqtt_tls; +extern ESPEasy_WiFiClientSecure* mqtt_tls; # endif // ifdef ESP32 # ifdef ESP8266 -extern BearSSL::WiFiClientSecure mqtt_tls; +extern BearSSL::WiFiClientSecure* mqtt_tls; extern BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 -extern char *mqtt_rootCA; +extern String mqtt_rootCA; # endif // ifdef USE_MQTT_TLS extern PubSubClient MQTTclient; extern bool MQTTclient_should_reconnect; diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp new file mode 100644 index 0000000000..f84c5aeb40 --- /dev/null +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp @@ -0,0 +1,341 @@ +#include "../Helpers/ESPEasy_WiFiClientSecure.h" + +/* + ESPEasy_WiFiClientSecure.cpp - Client Secure class for ESP32 + Copyright (c) 2016 Hristo Gochkov All right reserved. + Additions Copyright (C) 2017 Evandro Luis Copercini. + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include + +#undef connect +#undef write +#undef read + + +ESPEasy_WiFiClientSecure::ESPEasy_WiFiClientSecure() +{ + _connected = false; + + sslclient = new ESPEasy_sslclient_context; + ssl_init(sslclient); + sslclient->socket = -1; + sslclient->handshake_timeout = 120000; + _use_insecure = false; + _CA_cert = NULL; + _cert = NULL; + _private_key = NULL; + _pskIdent = NULL; + _psKey = NULL; + next = NULL; +} + + +ESPEasy_WiFiClientSecure::ESPEasy_WiFiClientSecure(int sock) +{ + _connected = false; + _timeout = 0; + + sslclient = new ESPEasy_sslclient_context; + ssl_init(sslclient); + sslclient->socket = sock; + sslclient->handshake_timeout = 120000; + + if (sock >= 0) { + _connected = true; + } + + _CA_cert = NULL; + _cert = NULL; + _private_key = NULL; + _pskIdent = NULL; + _psKey = NULL; + next = NULL; +} + +ESPEasy_WiFiClientSecure::~ESPEasy_WiFiClientSecure() +{ + stop(); + delete sslclient; +} + +ESPEasy_WiFiClientSecure &ESPEasy_WiFiClientSecure::operator=(const ESPEasy_WiFiClientSecure &other) +{ + stop(); + sslclient->socket = other.sslclient->socket; + _connected = other._connected; + return *this; +} + +void ESPEasy_WiFiClientSecure::stop() +{ + if (sslclient->socket >= 0) { + close(sslclient->socket); + sslclient->socket = -1; + _connected = false; + _peek = -1; + } + stop_ssl_socket(sslclient, _CA_cert, _cert, _private_key); +} + +int ESPEasy_WiFiClientSecure::connect(IPAddress ip, uint16_t port) +{ + if (_pskIdent && _psKey) + return connect(ip, port, _pskIdent, _psKey); + return connect(ip, port, _CA_cert, _cert, _private_key); +} + +int ESPEasy_WiFiClientSecure::connect(IPAddress ip, uint16_t port, int32_t timeout){ + _timeout = timeout; + return connect(ip, port); +} + +int ESPEasy_WiFiClientSecure::connect(const char *host, uint16_t port) +{ + if (_pskIdent && _psKey) + return connect(host, port, _pskIdent, _psKey); + return connect(host, port, _CA_cert, _cert, _private_key); +} + +int ESPEasy_WiFiClientSecure::connect(const char *host, uint16_t port, int32_t timeout){ + _timeout = timeout; + return connect(host, port); +} + +int ESPEasy_WiFiClientSecure::connect(IPAddress ip, uint16_t port, const char *CA_cert, const char *cert, const char *private_key) +{ + return connect(ip.toString().c_str(), port, CA_cert, cert, private_key); +} + +int ESPEasy_WiFiClientSecure::connect(const char *host, uint16_t port, const char *CA_cert, const char *cert, const char *private_key) +{ + if(_timeout > 0){ + sslclient->handshake_timeout = _timeout; + } + int ret = start_ssl_client(sslclient, host, port, _timeout, CA_cert, cert, private_key, NULL, NULL, _use_insecure); + _lastError = ret; + if (ret < 0) { + log_e("start_ssl_client: %d", ret); + stop(); + return 0; + } + _connected = true; + return 1; +} + +int ESPEasy_WiFiClientSecure::connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psKey) { + return connect(ip.toString().c_str(), port, pskIdent, psKey); +} + +int ESPEasy_WiFiClientSecure::connect(const char *host, uint16_t port, const char *pskIdent, const char *psKey) { + log_v("start_ssl_client with PSK"); + if(_timeout > 0){ + sslclient->handshake_timeout = _timeout; + } + int ret = start_ssl_client(sslclient, host, port, _timeout, NULL, NULL, NULL, pskIdent, psKey, _use_insecure); + _lastError = ret; + if (ret < 0) { + log_e("start_ssl_client: %d", ret); + stop(); + return 0; + } + _connected = true; + return 1; +} + +int ESPEasy_WiFiClientSecure::peek(){ + if(_peek >= 0){ + return _peek; + } + _peek = timedRead(); + return _peek; +} + +size_t ESPEasy_WiFiClientSecure::write(uint8_t data) +{ + return write(&data, 1); +} + +int ESPEasy_WiFiClientSecure::read() +{ + uint8_t data = -1; + int res = read(&data, 1); + if (res < 0) { + return res; + } + return data; +} + +size_t ESPEasy_WiFiClientSecure::write(const uint8_t *buf, size_t size) +{ + if (!_connected) { + return 0; + } + int res = send_ssl_data(sslclient, buf, size); + if (res < 0) { + stop(); + res = 0; + } + return res; +} + +int ESPEasy_WiFiClientSecure::read(uint8_t *buf, size_t size) +{ + int peeked = 0; + int avail = available(); + if ((!buf && size) || avail <= 0) { + return -1; + } + if(!size){ + return 0; + } + if(_peek >= 0){ + buf[0] = _peek; + _peek = -1; + size--; + avail--; + if(!size || !avail){ + return 1; + } + buf++; + peeked = 1; + } + + int res = get_ssl_receive(sslclient, buf, size); + if (res < 0) { + stop(); + return peeked?peeked:res; + } + return res + peeked; +} + +int ESPEasy_WiFiClientSecure::available() +{ + int peeked = (_peek >= 0); + if (!_connected) { + return peeked; + } + int res = data_to_read(sslclient); + if (res < 0) { + stop(); + return peeked?peeked:res; + } + return res+peeked; +} + +uint8_t ESPEasy_WiFiClientSecure::connected() +{ + uint8_t dummy = 0; + read(&dummy, 0); + + return _connected; +} + +void ESPEasy_WiFiClientSecure::setInsecure() +{ + _CA_cert = NULL; + _cert = NULL; + _private_key = NULL; + _pskIdent = NULL; + _psKey = NULL; + _use_insecure = true; +} + +void ESPEasy_WiFiClientSecure::setCACert (const char *rootCA) +{ + _CA_cert = rootCA; +} + +void ESPEasy_WiFiClientSecure::setCertificate (const char *client_ca) +{ + _cert = client_ca; +} + +void ESPEasy_WiFiClientSecure::setPrivateKey (const char *private_key) +{ + _private_key = private_key; +} + +void ESPEasy_WiFiClientSecure::setPreSharedKey(const char *pskIdent, const char *psKey) { + _pskIdent = pskIdent; + _psKey = psKey; +} + +bool ESPEasy_WiFiClientSecure::verify(const char* fp, const char* domain_name) +{ + if (!sslclient) + return false; + + return verify_ssl_fingerprint(sslclient, fp, domain_name); +} + +char *ESPEasy_WiFiClientSecure::_streamLoad(Stream& stream, size_t size) { + char *dest = (char*)malloc(size+1); + if (!dest) { + return nullptr; + } + if (size != stream.readBytes(dest, size)) { + free(dest); + dest = nullptr; + return nullptr; + } + dest[size] = '\0'; + return dest; +} + +bool ESPEasy_WiFiClientSecure::loadCACert(Stream& stream, size_t size) { + char *dest = _streamLoad(stream, size); + bool ret = false; + if (dest) { + setCACert(dest); + ret = true; + } + return ret; +} + +bool ESPEasy_WiFiClientSecure::loadCertificate(Stream& stream, size_t size) { + char *dest = _streamLoad(stream, size); + bool ret = false; + if (dest) { + setCertificate(dest); + ret = true; + } + return ret; +} + +bool ESPEasy_WiFiClientSecure::loadPrivateKey(Stream& stream, size_t size) { + char *dest = _streamLoad(stream, size); + bool ret = false; + if (dest) { + setPrivateKey(dest); + ret = true; + } + return ret; +} + +int ESPEasy_WiFiClientSecure::lastError(char *buf, const size_t size) +{ + if (!_lastError) { + return 0; + } + mbedtls_strerror(_lastError, buf, size); + return _lastError; +} + +void ESPEasy_WiFiClientSecure::setHandshakeTimeout(unsigned long handshake_timeout) +{ + sslclient->handshake_timeout = handshake_timeout * 1000; +} \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.h b/src/src/Helpers/ESPEasy_WiFiClientSecure.h new file mode 100644 index 0000000000..08e129446b --- /dev/null +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.h @@ -0,0 +1,113 @@ +/* + ESPEasy_WiFiClientSecure.h - Base class that provides Client SSL to ESP32 + Copyright (c) 2011 Adrian McEwen. All right reserved. + Additions Copyright (C) 2017 Evandro Luis Copercini. + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + Almost literal copy of https://github.com/brokentoaster/arduino-esp32/blob/master/libraries/WiFiClientSecure/src/WiFiClientSecure.h + Changed into "ESPEasy" version to incorporate some fixes + for memory leaks not yet present in the "older" core versions. +*/ + +#ifndef ESPEASY_WiFiClientSecure_h +#define ESPEASY_WiFiClientSecure_h +#include +#include +#include +#include "../Helpers/ESPEasy_ssl_client.h" + +class ESPEasy_WiFiClientSecure : public WiFiClient +{ +protected: + ESPEasy_sslclient_context *sslclient; + + int _lastError = 0; + int _peek = -1; + int _timeout = 0; + bool _use_insecure; + const char *_CA_cert; + const char *_cert; + const char *_private_key; + const char *_pskIdent; // identity for PSK cipher suites + const char *_psKey; // key in hex for PSK cipher suites + +public: + ESPEasy_WiFiClientSecure *next; + ESPEasy_WiFiClientSecure(); + ESPEasy_WiFiClientSecure(int socket); + ~ESPEasy_WiFiClientSecure(); + int connect(IPAddress ip, uint16_t port); + int connect(IPAddress ip, uint16_t port, int32_t timeout); + int connect(const char *host, uint16_t port); + int connect(const char *host, uint16_t port, int32_t timeout); + int connect(IPAddress ip, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key); + int connect(const char *host, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key); + int connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psKey); + int connect(const char *host, uint16_t port, const char *pskIdent, const char *psKey); + int peek(); + size_t write(uint8_t data); + size_t write(const uint8_t *buf, size_t size); + int available(); + int read(); + int read(uint8_t *buf, size_t size); + void flush() {} + void stop(); + uint8_t connected(); + int lastError(char *buf, const size_t size); + void setInsecure(); // Don't validate the chain, just accept whatever is given. VERY INSECURE! + void setPreSharedKey(const char *pskIdent, const char *psKey); // psKey in Hex + void setCACert(const char *rootCA); + void setCertificate(const char *client_ca); + void setPrivateKey (const char *private_key); + bool loadCACert(Stream& stream, size_t size); + bool loadCertificate(Stream& stream, size_t size); + bool loadPrivateKey(Stream& stream, size_t size); + bool verify(const char* fingerprint, const char* domain_name); + void setHandshakeTimeout(unsigned long handshake_timeout); + + int setTimeout(uint32_t seconds){ return 0; } + + operator bool() + { + return connected(); + } + ESPEasy_WiFiClientSecure &operator=(const ESPEasy_WiFiClientSecure &other); + bool operator==(const bool value) + { + return bool() == value; + } + bool operator!=(const bool value) + { + return bool() != value; + } + bool operator==(const ESPEasy_WiFiClientSecure &); + bool operator!=(const ESPEasy_WiFiClientSecure &rhs) + { + return !this->operator==(rhs); + }; + + int socket() + { + return sslclient->socket = -1; + } + +private: + char *_streamLoad(Stream& stream, size_t size); + + //friend class WiFiServer; + using Print::write; +}; + +#endif \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_ssl_client.cpp b/src/src/Helpers/ESPEasy_ssl_client.cpp new file mode 100644 index 0000000000..4ff9996b3b --- /dev/null +++ b/src/src/Helpers/ESPEasy_ssl_client.cpp @@ -0,0 +1,530 @@ +#include "../Helpers/ESPEasy_ssl_client.h" + +/* Provide SSL/TLS functions to ESP32 with Arduino IDE +* +* Adapted from the ssl_client1 example of mbedtls. +* +* Original Copyright (C) 2006-2015, ARM Limited, All Rights Reserved, Apache 2.0 License. +* Additions Copyright (C) 2017 Evandro Luis Copercini, Apache 2.0 License. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED +# error "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher" +#endif + +const char *ESPEasy_pers = "esp32-tls"; + +static int _handle_error(int err, const char * file, int line) +{ + if(err == -30848){ + return err; + } +#ifdef MBEDTLS_ERROR_C + char error_buf[100]; + mbedtls_strerror(err, error_buf, 100); + log_e("[%s():%d]: (%d) %s", file, line, err, error_buf); +#else + log_e("[%s():%d]: code %d", file, line, err); +#endif + return err; +} + +#define handle_error(e) _handle_error(e, __FUNCTION__, __LINE__) + +ESPEasy_sslclient_context::ESPEasy_sslclient_context() +{ + memset(&ssl_ctx, 0, sizeof(ssl_ctx)); + memset(&ssl_conf, 0, sizeof(ssl_conf)); + memset(&drbg_ctx, 0, sizeof(drbg_ctx)); + memset(&entropy_ctx, 0, sizeof(entropy_ctx)); + memset(&ca_cert, 0, sizeof(ca_cert)); + memset(&client_cert, 0, sizeof(client_cert)); + memset(&client_key, 0, sizeof(client_key)); +} + + +ESPEasy_sslclient_context::~ESPEasy_sslclient_context() +{ + free_ca_cert(); + free_client_cert(); + free_client_key(); +} + + +void ESPEasy_sslclient_context::free_ca_cert() +{ +// if (ca_cert.p != nullptr) { + if (ca_cert_init) { + ca_cert_init = false; + } + mbedtls_x509_crt_free(&ca_cert); +// } +} + +void ESPEasy_sslclient_context::free_client_cert() +{ + if (client_cert_init) { + client_cert_init = false; + } + mbedtls_x509_crt_free(&client_cert); +// } +} + +void ESPEasy_sslclient_context::free_client_key() +{ + if (client_key_init) { + client_key_init = false; + } + mbedtls_pk_free(&client_key); +// } +} + + +void ssl_init(ESPEasy_sslclient_context *ssl_client) +{ + mbedtls_ssl_free(&ssl_client->ssl_ctx); + mbedtls_ssl_config_free(&ssl_client->ssl_conf); + mbedtls_ctr_drbg_free(&ssl_client->drbg_ctx); + + mbedtls_ssl_init(&ssl_client->ssl_ctx); + mbedtls_ssl_config_init(&ssl_client->ssl_conf); + mbedtls_ctr_drbg_init(&ssl_client->drbg_ctx); +} + + +int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure) +{ + char buf[512]; + int ret, flags; + int enable = 1; + log_v("Free internal heap before TLS %u", ESP.getFreeHeap()); + + if (rootCABuff == NULL && pskIdent == NULL && psKey == NULL && !insecure) { + return -1; + } + + log_v("Starting socket"); + ssl_client->socket = -1; + + ssl_client->socket = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (ssl_client->socket < 0) { + log_e("ERROR opening socket"); + return ssl_client->socket; + } + + IPAddress srv((uint32_t)0); + if(!WiFiGenericClass::hostByName(host, srv)){ + return -1; + } + + struct sockaddr_in serv_addr; + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = srv; + serv_addr.sin_port = htons(port); + + if (lwip_connect(ssl_client->socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == 0) { + if(timeout <= 0){ + timeout = 30000; // Milli seconds. + } + timeval so_timeout = { .tv_sec = timeout / 1000, .tv_usec = (timeout % 1000) * 1000 }; + +#define ROE(x,msg) { if (((x)<0)) { log_e("LWIP Socket config of " msg " failed."); return -1; }} + ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_RCVTIMEO, &so_timeout, sizeof(so_timeout)),"SO_RCVTIMEO"); + ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_SNDTIMEO, &so_timeout, sizeof(so_timeout)),"SO_SNDTIMEO"); + + ROE(lwip_setsockopt(ssl_client->socket, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)),"TCP_NODELAY"); + ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)),"SO_KEEPALIVE"); + } else { + log_e("Connect to Server failed!"); + return -1; + } + + fcntl( ssl_client->socket, F_SETFL, fcntl( ssl_client->socket, F_GETFL, 0 ) | O_NONBLOCK ); + + log_v("Seeding the random number generator"); + mbedtls_entropy_free(&ssl_client->entropy_ctx); + mbedtls_entropy_init(&ssl_client->entropy_ctx); + + ret = mbedtls_ctr_drbg_seed(&ssl_client->drbg_ctx, mbedtls_entropy_func, + &ssl_client->entropy_ctx, (const unsigned char *) ESPEasy_pers, strlen(ESPEasy_pers)); + if (ret < 0) { + return handle_error(ret); + } + + log_v("Setting up the SSL/TLS structure..."); + + if ((ret = mbedtls_ssl_config_defaults(&ssl_client->ssl_conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + return handle_error(ret); + } + + // MBEDTLS_SSL_VERIFY_REQUIRED if a CA certificate is defined on Arduino IDE and + // MBEDTLS_SSL_VERIFY_NONE if not. + + if (insecure) { + mbedtls_ssl_conf_authmode(&ssl_client->ssl_conf, MBEDTLS_SSL_VERIFY_NONE); + log_i("WARNING: Skipping SSL Verification. INSECURE!"); + } else if (rootCABuff != NULL) { + log_v("Loading CA cert"); + mbedtls_x509_crt_init(&ssl_client->ca_cert); + ssl_client->ca_cert_init = true; + mbedtls_ssl_conf_authmode(&ssl_client->ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED); + ret = mbedtls_x509_crt_parse(&ssl_client->ca_cert, (const unsigned char *)rootCABuff, strlen(rootCABuff) + 1); + mbedtls_ssl_conf_ca_chain(&ssl_client->ssl_conf, &ssl_client->ca_cert, NULL); + //mbedtls_ssl_conf_verify(&ssl_client->ssl_ctx, my_verify, NULL ); + if (ret < 0) { + // free the ca_cert in the case parse failed, otherwise, the old ca_cert still in the heap memory, that lead to "out of memory" crash. + ssl_client->free_ca_cert(); + return handle_error(ret); + } + } else if (pskIdent != NULL && psKey != NULL) { + log_v("Setting up PSK"); + // convert PSK from hex to binary + if ((strlen(psKey) & 1) != 0 || strlen(psKey) > 2*MBEDTLS_PSK_MAX_LEN) { + log_e("pre-shared key not valid hex or too long"); + return -1; + } + unsigned char psk[MBEDTLS_PSK_MAX_LEN]; + size_t psk_len = strlen(psKey)/2; + for (int j=0; j= '0' && c <= '9') c -= '0'; + else if (c >= 'A' && c <= 'F') c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') c -= 'a' - 10; + else return -1; + psk[j/2] = c<<4; + c = psKey[j+1]; + if (c >= '0' && c <= '9') c -= '0'; + else if (c >= 'A' && c <= 'F') c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') c -= 'a' - 10; + else return -1; + psk[j/2] |= c; + } + // set mbedtls config + ret = mbedtls_ssl_conf_psk(&ssl_client->ssl_conf, psk, psk_len, + (const unsigned char *)pskIdent, strlen(pskIdent)); + if (ret != 0) { + log_e("mbedtls_ssl_conf_psk returned %d", ret); + return handle_error(ret); + } + } else { + return -1; + } + + if (!insecure && cli_cert != NULL && cli_key != NULL) { + mbedtls_x509_crt_init(&ssl_client->client_cert); + mbedtls_pk_init(&ssl_client->client_key); + + + log_v("Loading CRT cert"); + + ret = mbedtls_x509_crt_parse(&ssl_client->client_cert, (const unsigned char *)cli_cert, strlen(cli_cert) + 1); + ssl_client->client_cert_init = true; + if (ret < 0) { + // free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash. + ssl_client->free_client_cert(); + return handle_error(ret); + } + + log_v("Loading private key"); + ret = mbedtls_pk_parse_key(&ssl_client->client_key, (const unsigned char *)cli_key, strlen(cli_key) + 1, NULL, 0); + ssl_client->client_key_init = true; + + if (ret != 0) { + return handle_error(ret); + } + + mbedtls_ssl_conf_own_cert(&ssl_client->ssl_conf, &ssl_client->client_cert, &ssl_client->client_key); + } + + log_v("Setting hostname for TLS session..."); + + // Hostname set here should match CN in server certificate + if((ret = mbedtls_ssl_set_hostname(&ssl_client->ssl_ctx, host)) != 0){ + return handle_error(ret); + } + + mbedtls_ssl_conf_rng(&ssl_client->ssl_conf, mbedtls_ctr_drbg_random, &ssl_client->drbg_ctx); + + if ((ret = mbedtls_ssl_setup(&ssl_client->ssl_ctx, &ssl_client->ssl_conf)) != 0) { + return handle_error(ret); + } + + mbedtls_ssl_set_bio(&ssl_client->ssl_ctx, &ssl_client->socket, mbedtls_net_send, mbedtls_net_recv, NULL ); + + log_v("Performing the SSL/TLS handshake..."); + unsigned long handshake_start_time=millis(); + while ((ret = mbedtls_ssl_handshake(&ssl_client->ssl_ctx)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + // ++++++++++ ADDED TO FIX MEMORY LEAK ON FAILED CONNECTION ++++++++++ + ssl_client->free_client_key(); + ssl_client->free_ca_cert(); + ssl_client->free_client_cert(); + // ++++++++++ END ++++++++++ + return handle_error(ret); + } + if ((millis()-handshake_start_time) > ssl_client->handshake_timeout) { + // ++++++++++ ADDED TO FIX MEMORY LEAK ON FAILED CONNECTION ++++++++++ + ssl_client->free_client_key(); + ssl_client->free_ca_cert(); + ssl_client->free_client_cert(); + // ++++++++++ END ++++++++++ + return -1; + } + vTaskDelay(2);//2 ticks + } + + + if (cli_cert != NULL && cli_key != NULL) { + log_d("Protocol is %s Ciphersuite is %s", mbedtls_ssl_get_version(&ssl_client->ssl_ctx), mbedtls_ssl_get_ciphersuite(&ssl_client->ssl_ctx)); + if ((ret = mbedtls_ssl_get_record_expansion(&ssl_client->ssl_ctx)) >= 0) { + log_d("Record expansion is %d", ret); + } else { + log_w("Record expansion is unknown (compression)"); + } + } + + log_v("Verifying peer X.509 certificate..."); + + if ((flags = mbedtls_ssl_get_verify_result(&ssl_client->ssl_ctx)) != 0) { + memset(buf, 0, sizeof(buf)); + mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags); + log_e("Failed to verify peer certificate! verification info: %s", buf); + stop_ssl_socket(ssl_client, rootCABuff, cli_cert, cli_key); //It's not safe continue. + // ++++++++++ ADDED TO FIX MEMORY LEAK ON FAILED CONNECTION ++++++++++ + ssl_client->free_client_key(); + ssl_client->free_ca_cert(); + ssl_client->free_client_cert(); + // ++++++++++ END ++++++++++ + + return handle_error(ret); + } else { + log_v("Certificate verified."); + } + + ssl_client->free_ca_cert(); + ssl_client->free_client_cert(); + ssl_client->free_client_key(); + + log_v("Free internal heap after TLS %u", ESP.getFreeHeap()); + + return ssl_client->socket; +} + + +void stop_ssl_socket(ESPEasy_sslclient_context *ssl_client, const char *rootCABuff, const char *cli_cert, const char *cli_key) +{ + log_v("Cleaning SSL connection."); + + if (ssl_client->socket >= 0) { + close(ssl_client->socket); + ssl_client->socket = -1; + } + + mbedtls_ssl_free(&ssl_client->ssl_ctx); + mbedtls_ssl_config_free(&ssl_client->ssl_conf); + mbedtls_ctr_drbg_free(&ssl_client->drbg_ctx); + mbedtls_entropy_free(&ssl_client->entropy_ctx); + ssl_client->free_ca_cert(); + ssl_client->free_client_cert(); + ssl_client->free_client_key(); +} + + +int data_to_read(ESPEasy_sslclient_context *ssl_client) +{ + int ret, res; + ret = mbedtls_ssl_read(&ssl_client->ssl_ctx, NULL, 0); + //log_e("RET: %i",ret); //for low level debug + res = mbedtls_ssl_get_bytes_avail(&ssl_client->ssl_ctx); + //log_e("RES: %i",res); //for low level debug + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) { + return handle_error(ret); + } + + return res; +} + +int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, uint16_t len) +{ + log_v("Writing HTTP request with %d bytes...", len); //for low level debug + int ret = -1; + + if ((ret = mbedtls_ssl_write(&ssl_client->ssl_ctx, data, len)) <= 0){ + log_v("Handling error %d", ret); //for low level debug + return handle_error(ret); + } else{ + log_v("Returning with %d bytes written", ret); //for low level debug + } + + return ret; +} + +int get_ssl_receive(ESPEasy_sslclient_context *ssl_client, uint8_t *data, int length) +{ + //log_d( "Reading HTTP response..."); //for low level debug + int ret = -1; + + ret = mbedtls_ssl_read(&ssl_client->ssl_ctx, data, length); + + //log_v( "%d bytes read", ret); //for low level debug + return ret; +} + +static bool parseHexNibble(char pb, uint8_t* res) +{ + if (pb >= '0' && pb <= '9') { + *res = (uint8_t) (pb - '0'); return true; + } else if (pb >= 'a' && pb <= 'f') { + *res = (uint8_t) (pb - 'a' + 10); return true; + } else if (pb >= 'A' && pb <= 'F') { + *res = (uint8_t) (pb - 'A' + 10); return true; + } + return false; +} + +// Compare a name from certificate and domain name, return true if they match +static bool matchName(const std::string& name, const std::string& domainName) +{ + size_t wildcardPos = name.find('*'); + if (wildcardPos == std::string::npos) { + // Not a wildcard, expect an exact match + return name == domainName; + } + + size_t firstDotPos = name.find('.'); + if (wildcardPos > firstDotPos) { + // Wildcard is not part of leftmost component of domain name + // Do not attempt to match (rfc6125 6.4.3.1) + return false; + } + if (wildcardPos != 0 || firstDotPos != 1) { + // Matching of wildcards such as baz*.example.com and b*z.example.com + // is optional. Maybe implement this in the future? + return false; + } + size_t domainNameFirstDotPos = domainName.find('.'); + if (domainNameFirstDotPos == std::string::npos) { + return false; + } + return domainName.substr(domainNameFirstDotPos) == name.substr(firstDotPos); +} + +// Verifies certificate provided by the peer to match specified SHA256 fingerprint +bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* fp, const char* domain_name) +{ + // Convert hex string to byte array + uint8_t fingerprint_local[32]; + int len = strlen(fp); + int pos = 0; + for (size_t i = 0; i < sizeof(fingerprint_local); ++i) { + while (pos < len && ((fp[pos] == ' ') || (fp[pos] == ':'))) { + ++pos; + } + if (pos > len - 2) { + log_d("pos:%d len:%d fingerprint too short", pos, len); + return false; + } + uint8_t high, low; + if (!parseHexNibble(fp[pos], &high) || !parseHexNibble(fp[pos+1], &low)) { + log_d("pos:%d len:%d invalid hex sequence: %c%c", pos, len, fp[pos], fp[pos+1]); + return false; + } + pos += 2; + fingerprint_local[i] = low | (high << 4); + } + + // Get certificate provided by the peer + const mbedtls_x509_crt* crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx); + + if (!crt) + { + log_d("could not fetch peer certificate"); + return false; + } + + // Calculate certificate's SHA256 fingerprint + uint8_t fingerprint_remote[32]; + mbedtls_sha256_context sha256_ctx; + mbedtls_sha256_init(&sha256_ctx); + mbedtls_sha256_starts(&sha256_ctx, false); + mbedtls_sha256_update(&sha256_ctx, crt->raw.p, crt->raw.len); + mbedtls_sha256_finish(&sha256_ctx, fingerprint_remote); + mbedtls_sha256_free(&sha256_ctx); + + // Check if fingerprints match + if (memcmp(fingerprint_local, fingerprint_remote, 32)) + { + log_d("fingerprint doesn't match"); + return false; + } + + // Additionally check if certificate has domain name if provided + if (domain_name) + return verify_ssl_dn(ssl_client, domain_name); + else + return true; +} + +// Checks if peer certificate has specified domain in CN or SANs +bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name) +{ + log_d("domain name: '%s'", (domain_name)?domain_name:"(null)"); + std::string domain_name_str(domain_name); + std::transform(domain_name_str.begin(), domain_name_str.end(), domain_name_str.begin(), ::tolower); + + // Get certificate provided by the peer + const mbedtls_x509_crt* crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx); + + // Check for domain name in SANs + const mbedtls_x509_sequence* san = &crt->subject_alt_names; + while (san != nullptr) + { + std::string san_str((const char*)san->buf.p, san->buf.len); + std::transform(san_str.begin(), san_str.end(), san_str.begin(), ::tolower); + + if (matchName(san_str, domain_name_str)) + return true; + + log_d("SAN '%s': no match", san_str.c_str()); + + // Fetch next SAN + san = san->next; + } + + // Check for domain name in CN + const mbedtls_asn1_named_data* common_name = &crt->subject; + while (common_name != nullptr) + { + // While iterating through DN objects, check for CN object + if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &common_name->oid)) + { + std::string common_name_str((const char*)common_name->val.p, common_name->val.len); + + if (matchName(common_name_str, domain_name_str)) + return true; + + log_d("CN '%s': not match", common_name_str.c_str()); + } + + // Fetch next DN object + common_name = common_name->next; + } + + return false; +} \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_ssl_client.h b/src/src/Helpers/ESPEasy_ssl_client.h new file mode 100644 index 0000000000..46bc0d649f --- /dev/null +++ b/src/src/Helpers/ESPEasy_ssl_client.h @@ -0,0 +1,53 @@ +/* Provide SSL/TLS functions to ESP32 with Arduino IDE + * by Evandro Copercini - 2017 - Apache 2.0 License + */ + +#ifndef ESPEASY_ARD_SSL_H +#define ESPEASY_ARD_SSL_H +#include +#include +#include +#include +#include +#include +#include + +typedef struct ESPEasy_sslclient_context { + + ESPEasy_sslclient_context(); + + ~ESPEasy_sslclient_context(); + + void free_ca_cert(); + void free_client_cert(); + void free_client_key(); + + int socket = 0; + mbedtls_ssl_context ssl_ctx; + mbedtls_ssl_config ssl_conf; + + mbedtls_ctr_drbg_context drbg_ctx; + mbedtls_entropy_context entropy_ctx; + + mbedtls_x509_crt ca_cert; + mbedtls_x509_crt client_cert; + mbedtls_pk_context client_key; + + bool ca_cert_init = false; + bool client_cert_init = false; + bool client_key_init = false; + + unsigned long handshake_timeout = 120000; +} ESPEasy_sslclient_context; + + +void ssl_init(ESPEasy_sslclient_context *ssl_client); +int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure); +void stop_ssl_socket(ESPEasy_sslclient_context *ssl_client, const char *rootCABuff, const char *cli_cert, const char *cli_key); +int data_to_read(ESPEasy_sslclient_context *ssl_client); +int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, uint16_t len); +int get_ssl_receive(ESPEasy_sslclient_context *ssl_client, uint8_t *data, int length); +bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* fp, const char* domain_name); +bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name); + +#endif \ No newline at end of file From b9b7e45634e633951c546006e88e34df0a25643c Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 11:14:04 +0100 Subject: [PATCH 007/100] [MQTT TLS] Fix memory leak... finally --- src/src/ESPEasyCore/Controller.cpp | 32 +++++++++++++++----------- src/src/Helpers/ESPEasy_Storage.cpp | 21 ----------------- src/src/Helpers/ESPEasy_ssl_client.cpp | 22 +++--------------- src/src/Helpers/ESPEasy_ssl_client.h | 6 +---- 4 files changed, 22 insertions(+), 59 deletions(-) diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index ed30513d43..d3012112a9 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -188,12 +188,10 @@ bool MQTTConnect(controllerIndex_t controller_idx) if (MQTTclient.connected()) { MQTTclient.disconnect(); # ifdef USE_MQTT_TLS - /* if (mqtt_tls != nullptr) { delete mqtt_tls; mqtt_tls = nullptr; } - */ #endif } @@ -208,7 +206,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) mqtt_tls_last_errorstr = EMPTY_STRING; mqtt_tls_last_error = 0; const TLS_types TLS_type = ControllerSettings.TLStype(); - if (TLS_type != TLS_types::NoTLS) { + if (TLS_type != TLS_types::NoTLS && nullptr == mqtt_tls) { #ifdef ESP32 mqtt_tls = new ESPEasy_WiFiClientSecure; #endif @@ -219,6 +217,8 @@ bool MQTTConnect(controllerIndex_t controller_idx) if (mqtt_tls == nullptr) { addLog(LOG_LEVEL_ERROR, F("MQTT : Could not create TLS client, out of memory")); return false; + } else { + mqtt_rootCA.clear(); } } switch(TLS_type) { @@ -235,9 +235,12 @@ bool MQTTConnect(controllerIndex_t controller_idx) } case TLS_types::TLS_CA_CERT: { -// mqtt_rootCA.clear(); - if (mqtt_rootCA.isEmpty()) + mqtt_rootCA.clear(); + bool certLoaded = false; + if (mqtt_rootCA.isEmpty()) { LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_rootCA); + certLoaded = true; + } { static int previousFree = FreeMem(); @@ -255,15 +258,16 @@ bool MQTTConnect(controllerIndex_t controller_idx) previousFree = freemem; } - - if (mqtt_rootCA.length() > 0) { - #ifdef ESP32 - mqtt_tls->setCACert(mqtt_rootCA.c_str()); - #endif - #ifdef ESP8266 - mqtt_X509List.append(mqtt_rootCA.c_str()); - mqtt_tls->setTrustAnchors(&mqtt_X509List); - #endif + if (certLoaded) { + if (mqtt_rootCA.length() > 0) { + #ifdef ESP32 + mqtt_tls->setCACert(mqtt_rootCA.c_str()); + #endif + #ifdef ESP8266 + mqtt_X509List.append(mqtt_rootCA.c_str()); + mqtt_tls->setTrustAnchors(&mqtt_X509List); + #endif + } } break; } diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 874ddfdeda..e6fcdc8070 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -1104,10 +1104,6 @@ String LoadCertificate(const String& fname, String& certificate) } f.close(); - String analyse = F("Cleanup: Before: "); - analyse += certificate.length(); - analyse += F(" After: "); - if (!cleanupCertificate(certificate, changed)) { certificate.clear(); #ifndef BUILD_NO_DEBUG @@ -1118,23 +1114,6 @@ String LoadCertificate(const String& fname, String& certificate) } else if (changed) { //return SaveCertificate(fname, certificate); } -// addLog(LOG_LEVEL_INFO, F("After")); -// addLog(LOG_LEVEL_INFO, certificate); - static int previousFree = FreeMem(); - const int freemem = FreeMem(); - - analyse += certificate.length(); - analyse += changed ? F(" changed") : F(" same"); - analyse += F(" free memory: "); - analyse += freemem; - analyse += F(" largest free block: "); - analyse += getMaxFreeBlock(); - - analyse += F(" Difference: "); - analyse += previousFree - freemem; - - addLog(LOG_LEVEL_INFO, analyse); - previousFree = freemem; } return EMPTY_STRING; diff --git a/src/src/Helpers/ESPEasy_ssl_client.cpp b/src/src/Helpers/ESPEasy_ssl_client.cpp index 4ff9996b3b..baa7b67b1c 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.cpp +++ b/src/src/Helpers/ESPEasy_ssl_client.cpp @@ -65,30 +65,17 @@ ESPEasy_sslclient_context::~ESPEasy_sslclient_context() void ESPEasy_sslclient_context::free_ca_cert() { -// if (ca_cert.p != nullptr) { - if (ca_cert_init) { - ca_cert_init = false; - } - mbedtls_x509_crt_free(&ca_cert); -// } + mbedtls_x509_crt_free(&ca_cert); } void ESPEasy_sslclient_context::free_client_cert() { - if (client_cert_init) { - client_cert_init = false; - } - mbedtls_x509_crt_free(&client_cert); -// } + mbedtls_x509_crt_free(&client_cert); } void ESPEasy_sslclient_context::free_client_key() { - if (client_key_init) { - client_key_init = false; - } - mbedtls_pk_free(&client_key); -// } + mbedtls_pk_free(&client_key); } @@ -182,7 +169,6 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui } else if (rootCABuff != NULL) { log_v("Loading CA cert"); mbedtls_x509_crt_init(&ssl_client->ca_cert); - ssl_client->ca_cert_init = true; mbedtls_ssl_conf_authmode(&ssl_client->ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED); ret = mbedtls_x509_crt_parse(&ssl_client->ca_cert, (const unsigned char *)rootCABuff, strlen(rootCABuff) + 1); mbedtls_ssl_conf_ca_chain(&ssl_client->ssl_conf, &ssl_client->ca_cert, NULL); @@ -234,7 +220,6 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui log_v("Loading CRT cert"); ret = mbedtls_x509_crt_parse(&ssl_client->client_cert, (const unsigned char *)cli_cert, strlen(cli_cert) + 1); - ssl_client->client_cert_init = true; if (ret < 0) { // free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash. ssl_client->free_client_cert(); @@ -243,7 +228,6 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui log_v("Loading private key"); ret = mbedtls_pk_parse_key(&ssl_client->client_key, (const unsigned char *)cli_key, strlen(cli_key) + 1, NULL, 0); - ssl_client->client_key_init = true; if (ret != 0) { return handle_error(ret); diff --git a/src/src/Helpers/ESPEasy_ssl_client.h b/src/src/Helpers/ESPEasy_ssl_client.h index 46bc0d649f..9704b3ff80 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.h +++ b/src/src/Helpers/ESPEasy_ssl_client.h @@ -22,7 +22,7 @@ typedef struct ESPEasy_sslclient_context { void free_client_cert(); void free_client_key(); - int socket = 0; + int socket = -1; mbedtls_ssl_context ssl_ctx; mbedtls_ssl_config ssl_conf; @@ -33,10 +33,6 @@ typedef struct ESPEasy_sslclient_context { mbedtls_x509_crt client_cert; mbedtls_pk_context client_key; - bool ca_cert_init = false; - bool client_cert_init = false; - bool client_key_init = false; - unsigned long handshake_timeout = 120000; } ESPEasy_sslclient_context; From 53fadc5134680d1f25883c62b2cdeaa0db028294 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 12:06:20 +0100 Subject: [PATCH 008/100] [MQTT TLS] Clear CA certificate when starting a new connection Otherwise you may not always use the latest CA root certificate stored on the file system --- src/src/ESPEasyCore/Controller.cpp | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index d3012112a9..3728f2d195 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -192,6 +192,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) delete mqtt_tls; mqtt_tls = nullptr; } + mqtt_rootCA.clear(); #endif } @@ -213,12 +214,11 @@ bool MQTTConnect(controllerIndex_t controller_idx) #ifdef ESP8266 mqtt_tls = new BearSSL::WiFiClientSecure; #endif + mqtt_rootCA.clear(); if (mqtt_tls == nullptr) { addLog(LOG_LEVEL_ERROR, F("MQTT : Could not create TLS client, out of memory")); return false; - } else { - mqtt_rootCA.clear(); } } switch(TLS_type) { @@ -236,12 +236,6 @@ bool MQTTConnect(controllerIndex_t controller_idx) case TLS_types::TLS_CA_CERT: { mqtt_rootCA.clear(); - bool certLoaded = false; - if (mqtt_rootCA.isEmpty()) { - LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_rootCA); - certLoaded = true; - } - { static int previousFree = FreeMem(); const int freemem = FreeMem(); @@ -258,16 +252,16 @@ bool MQTTConnect(controllerIndex_t controller_idx) previousFree = freemem; } - if (certLoaded) { - if (mqtt_rootCA.length() > 0) { - #ifdef ESP32 - mqtt_tls->setCACert(mqtt_rootCA.c_str()); - #endif - #ifdef ESP8266 - mqtt_X509List.append(mqtt_rootCA.c_str()); - mqtt_tls->setTrustAnchors(&mqtt_X509List); - #endif - } + if (mqtt_rootCA.isEmpty()) { + LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_rootCA); + + #ifdef ESP32 + mqtt_tls->setCACert(mqtt_rootCA.c_str()); + #endif + #ifdef ESP8266 + mqtt_X509List.append(mqtt_rootCA.c_str()); + mqtt_tls->setTrustAnchors(&mqtt_X509List); + #endif } break; } From 6ca2c6f5c8824d2db835547a44f5b33139ec7a4b Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 12:18:06 +0100 Subject: [PATCH 009/100] [Cleanup] Fix lots of missing delete calls to free memory In a lot of places an object was allocated on the heap, but not always it was deleted thus leading to memory leaks. --- src/_C018.ino | 20 ++++-- src/_P016_IR.ino | 2 +- src/_P035_IRTX.ino | 30 ++++++-- src/_P038_NeoPixel.ino | 25 +++++-- src/_P041_NeoClock.ino | 19 +++-- src/_P042_Candle.ino | 21 ++++-- src/_P046_VentusW266.ino | 2 +- src/_P054_DMX512.ino | 8 ++- src/_P055_Chiming.ino | 84 +++++++++++----------- src/_P056_SDS011-Dust.ino | 8 ++- src/_P062_MPR121_KeyPad.ino | 4 +- src/_P065_DRF0299_MP3.ino | 9 +++ src/_P078_Eastron.ino | 4 +- src/_P088_HeatpumpIR.ino | 3 +- src/_P095_ILI9341.ino | 27 +++++-- src/_P096_eInk.ino | 44 ++++++++---- src/_P102_PZEM004Tv3.ino | 2 +- src/_P109_ThermOLED.ino | 4 +- src/src/DataStructs/Modbus.cpp | 15 +++- src/src/DataStructs/Modbus.h | 1 + src/src/Helpers/Networking.cpp | 10 ++- src/src/PluginStructs/P020_data_struct.cpp | 1 + src/src/PluginStructs/P044_data_struct.cpp | 1 + src/src/PluginStructs/P062_data_struct.cpp | 9 ++- src/src/PluginStructs/P062_data_struct.h | 1 + src/src/PluginStructs/P082_data_struct.cpp | 2 +- src/src/PluginStructs/P111_data_struct.cpp | 15 +++- src/src/PluginStructs/P111_data_struct.h | 3 +- 28 files changed, 261 insertions(+), 113 deletions(-) diff --git a/src/_C018.ino b/src/_C018.ino index c3b944d6a5..93f763c058 100644 --- a/src/_C018.ino +++ b/src/_C018.ino @@ -77,13 +77,25 @@ struct C018_data_struct { _baudrate = baudrate; // FIXME TD-er: Make force SW serial a proper setting. + if (C018_easySerial != nullptr) { + delete C018_easySerial; + } + C018_easySerial = new (std::nothrow) ESPeasySerial(static_cast(port), serial_rx, serial_tx, false, 64); if (C018_easySerial != nullptr) { - myLora = new rn2xx3(*C018_easySerial); - myLora->setAsyncMode(true); - myLora->setLastUsedJoinMode(joinIsOTAA); - triggerAutobaud(); + if (myLora != nullptr) { + delete myLora; + } + myLora = new (std::nothrow) rn2xx3(*C018_easySerial); + if (myLora == nullptr) { + delete C018_easySerial; + C018_easySerial = nullptr; + } else { + myLora->setAsyncMode(true); + myLora->setLastUsedJoinMode(joinIsOTAA); + triggerAutobaud(); + } } return isInitialized(); } diff --git a/src/_P016_IR.ino b/src/_P016_IR.ino index c9ff19c87a..f2229bc8d9 100644 --- a/src/_P016_IR.ino +++ b/src/_P016_IR.ino @@ -226,7 +226,7 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) addLog(LOG_LEVEL_INFO, F("INIT: IR RX")); addLog(LOG_LEVEL_INFO, F("IR lib Version: " _IRREMOTEESP8266_VERSION_)); } - irReceiver = new IRrecv(irPin, kCaptureBufferSize, P016_TIMEOUT, true); + irReceiver = new (std::nothrow) IRrecv(irPin, kCaptureBufferSize, P016_TIMEOUT, true); # ifdef PLUGIN_016_DEBUG addLog(LOG_LEVEL_INFO, F("P016_PLUGIN_INIT IR receiver created")); # endif // PLUGIN_016_DEBUG diff --git a/src/_P035_IRTX.ino b/src/_P035_IRTX.ino index bc6425632c..69b094e9dc 100644 --- a/src/_P035_IRTX.ino +++ b/src/_P035_IRTX.ino @@ -99,21 +99,23 @@ boolean Plugin_035(uint8_t function, struct EventStruct *event, String &command) case PLUGIN_INIT: { int irPin = CONFIG_PIN1; - if (Plugin_035_irSender == 0 && validGpio(irPin)) + if (Plugin_035_irSender == nullptr && validGpio(irPin)) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, F("INIT: IR TX")); addLog(LOG_LEVEL_INFO, F("IR lib Version: " _IRREMOTEESP8266_VERSION_)); addLog(LOG_LEVEL_INFO, String(F("Supported Protocols by IRSEND: ")) + listProtocols()); } - Plugin_035_irSender = new IRsend(irPin); - Plugin_035_irSender->begin(); // Start the sender + Plugin_035_irSender = new (std::nothrow) IRsend(irPin); + if (Plugin_035_irSender != nullptr) { + Plugin_035_irSender->begin(); // Start the sender + } } - if (Plugin_035_irSender != 0 && irPin == -1) + if (Plugin_035_irSender != nullptr && irPin == -1) { addLog(LOG_LEVEL_INFO, F("INIT: IR TX Removed")); delete Plugin_035_irSender; - Plugin_035_irSender = 0; + Plugin_035_irSender = nullptr; } #ifdef P016_P035_Extended_AC @@ -129,7 +131,7 @@ boolean Plugin_035(uint8_t function, struct EventStruct *event, String &command) { addLog(LOG_LEVEL_INFO, F("INIT AC: IR TX Removed")); delete Plugin_035_commonAc; - Plugin_035_commonAc = 0; + Plugin_035_commonAc = nullptr; } #endif @@ -137,6 +139,22 @@ boolean Plugin_035(uint8_t function, struct EventStruct *event, String &command) break; } + case PLUGIN_EXIT: + { + if (Plugin_035_irSender != nullptr) { + delete Plugin_035_irSender; + Plugin_035_irSender = nullptr; + } + #ifdef P016_P035_Extended_AC + if (Plugin_035_commonAc != nullptr) { + delete Plugin_035_commonAc; + Plugin_035_commonAc = nullptr; + } + #endif + success = true; + break; + } + case PLUGIN_WRITE: { String cmdCode = parseString(command,1); diff --git a/src/_P038_NeoPixel.ino b/src/_P038_NeoPixel.ino index bee1239d1e..564d98de72 100644 --- a/src/_P038_NeoPixel.ino +++ b/src/_P038_NeoPixel.ino @@ -31,7 +31,7 @@ #include -Adafruit_NeoPixel *Plugin_038_pixels; +Adafruit_NeoPixel *Plugin_038_pixels = nullptr; #define PLUGIN_038 #define PLUGIN_ID_038 38 @@ -94,23 +94,36 @@ boolean Plugin_038(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { - if (!Plugin_038_pixels) + if (Plugin_038_pixels == nullptr) { uint8_t striptype = PCONFIG(1); if (striptype == 1) - Plugin_038_pixels = new Adafruit_NeoPixel(PCONFIG(0), CONFIG_PIN1, NEO_GRB + NEO_KHZ800); + Plugin_038_pixels = new (std::nothrow) Adafruit_NeoPixel(PCONFIG(0), CONFIG_PIN1, NEO_GRB + NEO_KHZ800); else if (striptype == 2) - Plugin_038_pixels = new Adafruit_NeoPixel(PCONFIG(0), CONFIG_PIN1, NEO_GRBW + NEO_KHZ800); + Plugin_038_pixels = new (std::nothrow) Adafruit_NeoPixel(PCONFIG(0), CONFIG_PIN1, NEO_GRBW + NEO_KHZ800); else - Plugin_038_pixels = new Adafruit_NeoPixel(PCONFIG(0), CONFIG_PIN1, NEO_GRB + NEO_KHZ800); + Plugin_038_pixels = new (std::nothrow) Adafruit_NeoPixel(PCONFIG(0), CONFIG_PIN1, NEO_GRB + NEO_KHZ800); - Plugin_038_pixels->begin(); // This initializes the NeoPixel library. + if (Plugin_038_pixels != nullptr) { + Plugin_038_pixels->begin(); // This initializes the NeoPixel library. + } } MaxPixels = PCONFIG(0); + success = Plugin_038_pixels != nullptr; + break; + } + + case PLUGIN_EXIT: + { + if (Plugin_038_pixels != nullptr) { + delete Plugin_038_pixels; + Plugin_038_pixels = nullptr; + } success = true; break; } + case PLUGIN_WRITE: { if (Plugin_038_pixels) diff --git a/src/_P041_NeoClock.ino b/src/_P041_NeoClock.ino index f0cd6f45c4..7371a68aca 100644 --- a/src/_P041_NeoClock.ino +++ b/src/_P041_NeoClock.ino @@ -80,15 +80,26 @@ boolean Plugin_041(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { - if (!Plugin_041_pixels) + if (Plugin_041_pixels == nullptr) { - Plugin_041_pixels = new Adafruit_NeoPixel(NUM_LEDS, CONFIG_PIN1, NEO_GRB + NEO_KHZ800); - Plugin_041_pixels->begin(); // This initializes the NeoPixel library. + Plugin_041_pixels = new (std::nothrow) Adafruit_NeoPixel(NUM_LEDS, CONFIG_PIN1, NEO_GRB + NEO_KHZ800); + if (Plugin_041_pixels != nullptr) { + Plugin_041_pixels->begin(); // This initializes the NeoPixel library. + } } Plugin_041_red = PCONFIG(0); Plugin_041_green = PCONFIG(1); Plugin_041_blue = PCONFIG(2); - success = true; + success = Plugin_041_pixels != nullptr; + break; + } + + case PLUGIN_EXIT: + { + if (Plugin_041_pixels != nullptr) { + delete Plugin_041_pixels; + Plugin_041_pixels = nullptr; + } break; } diff --git a/src/_P042_Candle.ino b/src/_P042_Candle.ino index 88af364a96..fd4995e94c 100644 --- a/src/_P042_Candle.ino +++ b/src/_P042_Candle.ino @@ -273,10 +273,12 @@ boolean Plugin_042(uint8_t function, struct EventStruct *event, String& string) if (Candle_pixels) { delete Candle_pixels; } - Candle_pixels = new Adafruit_NeoPixel(NUM_PIXEL, CONFIG_PIN1, NEO_GRB + NEO_KHZ800); - SetPixelsBlack(); - Candle_pixels->setBrightness(Candle_bright); - Candle_pixels->begin(); + Candle_pixels = new (std::nothrow) Adafruit_NeoPixel(NUM_PIXEL, CONFIG_PIN1, NEO_GRB + NEO_KHZ800); + if (Candle_pixels != nullptr) { + SetPixelsBlack(); + Candle_pixels->setBrightness(Candle_bright); + Candle_pixels->begin(); + } #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { @@ -287,7 +289,16 @@ boolean Plugin_042(uint8_t function, struct EventStruct *event, String& string) #endif } - success = true; + success = Candle_pixels != nullptr; + break; + } + + case PLUGIN_EXIT: + { + if (Candle_pixels != nullptr) { + delete Candle_pixels; + Candle_pixels = nullptr; + } break; } diff --git a/src/_P046_VentusW266.ino b/src/_P046_VentusW266.ino index 93a7e916f6..09f196b7d0 100644 --- a/src/_P046_VentusW266.ino +++ b/src/_P046_VentusW266.ino @@ -279,7 +279,7 @@ boolean Plugin_046(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { if (!P046_data) { - P046_data = new P046_data_struct(); + P046_data = new (std::nothrow) P046_data_struct(); } uint8_t choice = PCONFIG(0); diff --git a/src/_P054_DMX512.ino b/src/_P054_DMX512.ino index 7e41c022d2..28956720a8 100644 --- a/src/_P054_DMX512.ino +++ b/src/_P054_DMX512.ino @@ -127,10 +127,12 @@ boolean Plugin_054(uint8_t function, struct EventStruct *event, String& string) if (Plugin_054_DMXBuffer) { delete [] Plugin_054_DMXBuffer; } - Plugin_054_DMXBuffer = new uint8_t[Plugin_054_DMXSize]; - memset(Plugin_054_DMXBuffer, 0, Plugin_054_DMXSize); + Plugin_054_DMXBuffer = new (std::nothrow) uint8_t[Plugin_054_DMXSize]; + if (Plugin_054_DMXBuffer != nullptr) { + memset(Plugin_054_DMXBuffer, 0, Plugin_054_DMXSize); + } - success = true; + success = Plugin_054_DMXBuffer != nullptr; break; } diff --git a/src/_P055_Chiming.ino b/src/_P055_Chiming.ino index c471cbf75a..d23e84bf1a 100644 --- a/src/_P055_Chiming.ino +++ b/src/_P055_Chiming.ino @@ -62,35 +62,20 @@ class CPlugin_055_Data { public: - long millisStateEnd; - long millisChimeTime; - long millisPauseTime; + long millisStateEnd = 0; + long millisChimeTime = 60; + long millisPauseTime = 400; - int pin[4]; - uint8_t lowActive; - uint8_t chimeClock; + int pin[4] = {0}; + uint8_t lowActive = false; + uint8_t chimeClock = true; - char FIFO[PLUGIN_055_FIFO_SIZE]; - uint8_t FIFO_IndexR; - uint8_t FIFO_IndexW; - - void Plugin_055_Data() - { - millisStateEnd = 0; - millisChimeTime = 60; - millisPauseTime = 400; - - for (uint8_t i=0; i<4; i++) - pin[i] = -1; - lowActive = false; - chimeClock = true; - - FIFO_IndexR = 0; - FIFO_IndexW = 0; - } + char FIFO[PLUGIN_055_FIFO_SIZE] = {0}; + uint8_t FIFO_IndexR = 0; + uint8_t FIFO_IndexW = 0; }; -static CPlugin_055_Data* Plugin_055_Data = NULL; +static CPlugin_055_Data* Plugin_055_Data = nullptr; boolean Plugin_055(uint8_t function, struct EventStruct *event, String& string) @@ -188,31 +173,42 @@ boolean Plugin_055(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { if (!Plugin_055_Data) - Plugin_055_Data = new CPlugin_055_Data(); + Plugin_055_Data = new (std::nothrow) CPlugin_055_Data(); - Plugin_055_Data->lowActive = Settings.TaskDevicePin1Inversed[event->TaskIndex]; - Plugin_055_Data->millisChimeTime = PCONFIG(0); - Plugin_055_Data->millisPauseTime = PCONFIG(1); - Plugin_055_Data->chimeClock = PCONFIG(2); + if (Plugin_055_Data != nullptr) { + Plugin_055_Data->lowActive = Settings.TaskDevicePin1Inversed[event->TaskIndex]; + Plugin_055_Data->millisChimeTime = PCONFIG(0); + Plugin_055_Data->millisPauseTime = PCONFIG(1); + Plugin_055_Data->chimeClock = PCONFIG(2); - String log = F("Chime: GPIO: "); - for (uint8_t i=0; i<4; i++) - { - int pin = Settings.TaskDevicePin[i][event->TaskIndex]; - Plugin_055_Data->pin[i] = pin; - if (pin >= 0) + String log = F("Chime: GPIO: "); + for (uint8_t i=0; i<4; i++) { - pinMode(pin, OUTPUT); - digitalWrite(pin, Plugin_055_Data->lowActive); + int pin = Settings.TaskDevicePin[i][event->TaskIndex]; + Plugin_055_Data->pin[i] = pin; + if (pin >= 0) + { + pinMode(pin, OUTPUT); + digitalWrite(pin, Plugin_055_Data->lowActive); + } + log += pin; + log += ' '; } - log += pin; - log += ' '; + if (Plugin_055_Data->lowActive) + log += F("!"); + addLog(LOG_LEVEL_INFO, log); + success = true; } - if (Plugin_055_Data->lowActive) - log += F("!"); - addLog(LOG_LEVEL_INFO, log); - success = true; + break; + } + + case PLUGIN_EXIT: + { + if (Plugin_055_Data != nullptr) { + delete Plugin_055_Data; + Plugin_055_Data = nullptr; + } break; } diff --git a/src/_P056_SDS011-Dust.ino b/src/_P056_SDS011-Dust.ino index 987e9a5a37..8d57cfeee0 100644 --- a/src/_P056_SDS011-Dust.ino +++ b/src/_P056_SDS011-Dust.ino @@ -22,7 +22,7 @@ #include "ESPEasy-Globals.h" -CjkSDS011 *Plugin_056_SDS = NULL; +CjkSDS011 *Plugin_056_SDS = nullptr; boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) @@ -105,12 +105,13 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { - if (Plugin_056_SDS) + if (Plugin_056_SDS) { delete Plugin_056_SDS; + } const int16_t serial_rx = CONFIG_PIN1; const int16_t serial_tx = CONFIG_PIN2; const ESPEasySerialPort port = static_cast(CONFIG_PORT); - Plugin_056_SDS = new CjkSDS011(port, serial_rx, serial_tx); + Plugin_056_SDS = new (std::nothrow) CjkSDS011(port, serial_rx, serial_tx); String log = F("SDS : Init OK ESP GPIO-pin RX:"); log += serial_rx; log += F(" TX:"); @@ -124,6 +125,7 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_EXIT: { // //FIXME: if this plugin is used more than once at the same time, things go horribly wrong :) + // FIXME TD-er: Must implement plugin_data_struct for this // // if (Plugin_056_SDS) // delete Plugin_056_SDS; diff --git a/src/_P062_MPR121_KeyPad.ino b/src/_P062_MPR121_KeyPad.ino index 3c8ad607f4..f15943828b 100644 --- a/src/_P062_MPR121_KeyPad.ino +++ b/src/_P062_MPR121_KeyPad.ino @@ -210,6 +210,7 @@ boolean Plugin_062(uint8_t function, struct EventStruct *event, String& string) SaveCustomTaskSettings(event->TaskIndex, reinterpret_cast(&(P062_data->StoredSettings)), sizeof(P062_data->StoredSettings)); if (!canCalibrate) { delete P062_data; + P062_data = nullptr; } else { bool clearCalibration = isFormItemChecked(F("p062_clear_calibrate")); if (clearCalibration) { @@ -232,11 +233,12 @@ boolean Plugin_062(uint8_t function, struct EventStruct *event, String& string) P062_data_struct *P062_data = static_cast(getPluginTaskData(event->TaskIndex)); if (nullptr != P062_data) { - success = true; if (!P062_data->init(event->TaskIndex, PCONFIG(0), PCONFIG(1), tbUseCalibration)) { clearPluginTaskData(event->TaskIndex); P062_data = nullptr; } else { + success = true; + uint8_t touch_treshold = PCONFIG(2); if(touch_treshold == 0) { touch_treshold = P062_DEFAULT_TOUCH_TRESHOLD; //default value diff --git a/src/_P065_DRF0299_MP3.ino b/src/_P065_DRF0299_MP3.ino index 480dbacdc9..0e80d0dacd 100644 --- a/src/_P065_DRF0299_MP3.ino +++ b/src/_P065_DRF0299_MP3.ino @@ -121,6 +121,15 @@ boolean Plugin_065(uint8_t function, struct EventStruct *event, String& string) break; } + case PLUGIN_EXIT: + { + if (P065_easySerial != nullptr) { + delete P065_easySerial; + P065_easySerial = nullptr; + } + break; + } + case PLUGIN_WRITE: { if (!P065_easySerial) { diff --git a/src/_P078_Eastron.ino b/src/_P078_Eastron.ino index 4116936094..924dbcdea7 100644 --- a/src/_P078_Eastron.ino +++ b/src/_P078_Eastron.ino @@ -217,7 +217,6 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { - Plugin_078_init = true; if (Plugin_078_SoftSerial != NULL) { delete Plugin_078_SoftSerial; Plugin_078_SoftSerial=NULL; @@ -233,9 +232,10 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string) delete Plugin_078_SDM; Plugin_078_SDM=NULL; } - Plugin_078_SDM = new SDM(*Plugin_078_SoftSerial, baudrate, P078_DEPIN); + Plugin_078_SDM = new (std::nothrow) SDM(*Plugin_078_SoftSerial, baudrate, P078_DEPIN); if (Plugin_078_SDM != nullptr) { Plugin_078_SDM->begin(); + Plugin_078_init = true; success = true; } break; diff --git a/src/_P088_HeatpumpIR.ino b/src/_P088_HeatpumpIR.ino index ef944840c3..55585d2b10 100644 --- a/src/_P088_HeatpumpIR.ino +++ b/src/_P088_HeatpumpIR.ino @@ -207,9 +207,10 @@ boolean Plugin_088(uint8_t function, struct EventStruct *event, String& string) { addLog(LOG_LEVEL_INFO, F("P088: Heatpump IR transmitter deactivated")); - if (Plugin_088_irSender != NULL) + if (Plugin_088_irSender != nullptr) { delete Plugin_088_irSender; + Plugin_088_irSender = nullptr; } break; diff --git a/src/_P095_ILI9341.ino b/src/_P095_ILI9341.ino index de35e94acb..65a6c101a0 100644 --- a/src/_P095_ILI9341.ino +++ b/src/_P095_ILI9341.ino @@ -261,16 +261,31 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) TFT_Settings.address_tft_dc = PIN(1); TFT_Settings.address_tft_rst = PIN(2); TFT_Settings.rotation = PCONFIG(1); + if (tft != nullptr) { + delete tft; + tft = nullptr; + } - tft = new Adafruit_ILI9341(TFT_Settings.address_tft_cs, TFT_Settings.address_tft_dc, TFT_Settings.address_tft_rst); - tft->begin(); - tft->setRotation(TFT_Settings.rotation); - tft->fillScreen(ILI9341_WHITE); - Plugin_095_printText("ESPEasy", 1, 1); - success = true; + tft = new (std::nothrow) Adafruit_ILI9341(TFT_Settings.address_tft_cs, TFT_Settings.address_tft_dc, TFT_Settings.address_tft_rst); + if (tft != nullptr) { + tft->begin(); + tft->setRotation(TFT_Settings.rotation); + tft->fillScreen(ILI9341_WHITE); + Plugin_095_printText("ESPEasy", 1, 1); + success = true; + } break; } + case PLUGIN_EXIT: + { + if (tft != nullptr) { + delete tft; + tft = nullptr; + } + break; + } + case PLUGIN_WRITE: { String tmpString = String(string); diff --git a/src/_P096_eInk.ino b/src/_P096_eInk.ino index dcfc425498..dc7521448c 100644 --- a/src/_P096_eInk.ino +++ b/src/_P096_eInk.ino @@ -285,20 +285,36 @@ boolean Plugin_096(uint8_t function, struct EventStruct *event, String& string) EPD_Settings.width = PCONFIG(2); EPD_Settings.height = PCONFIG(3); - eInkScreen = new LOLIN_IL3897(EPD_Settings.width, EPD_Settings.height, EPD_Settings.address_epd_dc, EPD_Settings.address_epd_rst, EPD_Settings.address_epd_cs, EPD_Settings.address_epd_busy); //hardware SPI - plugin_096_sequence_in_progress = false; - eInkScreen->begin(); - eInkScreen->clearBuffer(); - - eInkScreen->setTextColor(EPD_BLACK); - eInkScreen->setTextSize(3); - eInkScreen->println("ESP Easy"); - eInkScreen->setTextSize(2); - eInkScreen->println("eInk shield"); - eInkScreen->display(); - delay(100); - - success = true; + if (eInkScreen != nullptr) { + delete eInkScreen; + eInkScreen = nullptr; + } + + eInkScreen = new (std::nothrow) LOLIN_IL3897(EPD_Settings.width, EPD_Settings.height, EPD_Settings.address_epd_dc, EPD_Settings.address_epd_rst, EPD_Settings.address_epd_cs, EPD_Settings.address_epd_busy); //hardware SPI + if (eInkScreen != nullptr) { + plugin_096_sequence_in_progress = false; + eInkScreen->begin(); + eInkScreen->clearBuffer(); + + eInkScreen->setTextColor(EPD_BLACK); + eInkScreen->setTextSize(3); + eInkScreen->println("ESP Easy"); + eInkScreen->setTextSize(2); + eInkScreen->println("eInk shield"); + eInkScreen->display(); + delay(100); + + success = true; + } + break; + } + + case PLUGIN_EXIT: + { + if (eInkScreen != nullptr) { + delete eInkScreen; + eInkScreen = nullptr; + } break; } diff --git a/src/_P102_PZEM004Tv3.ino b/src/_P102_PZEM004Tv3.ino index cf6e9e9a28..9f0864fcbc 100644 --- a/src/_P102_PZEM004Tv3.ino +++ b/src/_P102_PZEM004Tv3.ino @@ -232,7 +232,7 @@ boolean Plugin_102(uint8_t function, struct EventStruct *event, String& string) } // Hardware serial is RX on 3 and TX on 1 - P102_PZEM_sensor = new PZEM004Tv30(port, rxPin, txPin); + P102_PZEM_sensor = new (std::nothrow) PZEM004Tv30(port, rxPin, txPin); // Sequence for changing PZEM address if (P102_PZEM_ADDR_SET == 1) // if address programming confirmed diff --git a/src/_P109_ThermOLED.ino b/src/_P109_ThermOLED.ino index 7cbd812dce..b466a10914 100644 --- a/src/_P109_ThermOLED.ino +++ b/src/_P109_ThermOLED.ino @@ -279,9 +279,9 @@ boolean Plugin_109(byte function, struct EventStruct *event, String& string) uint8_t OLED_address = Settings.TaskDevicePluginConfig[event->TaskIndex][0]; if (Settings.TaskDevicePluginConfig[event->TaskIndex][2] == 1) { - P109_display = new SSD1306Wire(OLED_address, Settings.Pin_i2c_sda, Settings.Pin_i2c_scl); + P109_display = new (std::nothrow) SSD1306Wire(OLED_address, Settings.Pin_i2c_sda, Settings.Pin_i2c_scl); } else { - P109_display = new SH1106Wire(OLED_address, Settings.Pin_i2c_sda, Settings.Pin_i2c_scl); + P109_display = new (std::nothrow) SH1106Wire(OLED_address, Settings.Pin_i2c_sda, Settings.Pin_i2c_scl); } P109_display->init(); // call to local override of init function P109_display->displayOn(); diff --git a/src/src/DataStructs/Modbus.cpp b/src/src/DataStructs/Modbus.cpp index 843b28b5e1..5155f095ba 100644 --- a/src/src/DataStructs/Modbus.cpp +++ b/src/src/DataStructs/Modbus.cpp @@ -8,13 +8,26 @@ Modbus::Modbus() : ModbusClient(nullptr), errcnt(0), timeout(0), TXRXstate(MODBUS_IDLE), RXavailable(0), payLoad(0) {} +Modbus::~Modbus() { + if (ModbusClient) { + ModbusClient->flush(); + ModbusClient->stop(); + delete (ModbusClient); + delay(1); + ModbusClient = nullptr; + } +} + bool Modbus::begin(uint8_t function, uint8_t ModbusID, uint16_t ModbusRegister, MODBUS_registerTypes_t type, char *IPaddress) { currentRegister = ModbusRegister; currentFunction = function; incomingValue = type; resultReceived = false; - ModbusClient = new WiFiClient(); + ModbusClient = new (std::nothrow) WiFiClient(); + if (ModbusClient == nullptr) { + return false; + } ModbusClient->setNoDelay(true); ModbusClient->setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); timeout = millis(); diff --git a/src/src/DataStructs/Modbus.h b/src/src/DataStructs/Modbus.h index a8f97104a1..316525d5dd 100644 --- a/src/src/DataStructs/Modbus.h +++ b/src/src/DataStructs/Modbus.h @@ -14,6 +14,7 @@ class Modbus { public: Modbus(void); + ~Modbus(); bool handle(); bool begin(uint8_t function, uint8_t ModbusID, diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index 747413d967..854a0d07d9 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -627,13 +627,17 @@ static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); bool SSDP_begin() { _pending = false; - if (_server) { + if (_server != nullptr) { _server->unref(); + // FIXME TD-er: Shouldn't this also call delete _server ? - _server = 0; + _server = nullptr; } - _server = new UdpContext; + _server = new (std::nothrow) UdpContext; + if (_server == nullptr) { + return false; + } _server->ref(); ip_addr_t ifaddr; diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index 4f19f0f7e8..c0fe27e33e 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -20,6 +20,7 @@ P020_Task::P020_Task(taskIndex_t taskIndex) : _taskIndex(taskIndex) { P020_Task::~P020_Task() { stopServer(); + serialEnd(); } bool P020_Task::serverActive(WiFiServer *server) { diff --git a/src/src/PluginStructs/P044_data_struct.cpp b/src/src/PluginStructs/P044_data_struct.cpp index ed1cd37a80..785a7ff21b 100644 --- a/src/src/PluginStructs/P044_data_struct.cpp +++ b/src/src/PluginStructs/P044_data_struct.cpp @@ -19,6 +19,7 @@ P044_Task::P044_Task() { P044_Task::~P044_Task() { stopServer(); + serialEnd(); } bool P044_Task::serverActive(WiFiServer *server) { diff --git a/src/src/PluginStructs/P062_data_struct.cpp b/src/src/PluginStructs/P062_data_struct.cpp index 21fd3671f9..924c79bbf6 100644 --- a/src/src/PluginStructs/P062_data_struct.cpp +++ b/src/src/PluginStructs/P062_data_struct.cpp @@ -14,6 +14,13 @@ P062_data_struct::P062_data_struct() { clearCalibrationData(); // Reset } +P062_data_struct::~P062_data_struct() { + if (keypad != nullptr) { + delete keypad; + keypad = nullptr; + } +} + bool P062_data_struct::init(taskIndex_t taskIndex, uint8_t i2c_addr, bool scancode, @@ -26,7 +33,7 @@ bool P062_data_struct::init(taskIndex_t taskIndex, _keepCalibrationData = keepCalibrationData; if (!keypad) { - keypad = new Adafruit_MPR121(); + keypad = new (std::nothrow) Adafruit_MPR121(); } if (keypad) { keypad->begin(_i2c_addr); diff --git a/src/src/PluginStructs/P062_data_struct.h b/src/src/PluginStructs/P062_data_struct.h index 7fc3c27989..92a9acca50 100644 --- a/src/src/PluginStructs/P062_data_struct.h +++ b/src/src/PluginStructs/P062_data_struct.h @@ -14,6 +14,7 @@ struct P062_data_struct : public PluginTaskData_base { public: P062_data_struct(); + ~P062_data_struct(); bool init(taskIndex_t taskIndex, uint8_t i2c_addr, bool scancode, diff --git a/src/src/PluginStructs/P082_data_struct.cpp b/src/src/PluginStructs/P082_data_struct.cpp index 95a7fd4877..29e7275f8e 100644 --- a/src/src/PluginStructs/P082_data_struct.cpp +++ b/src/src/PluginStructs/P082_data_struct.cpp @@ -51,7 +51,7 @@ bool P082_data_struct::init(ESPEasySerialPort port, const int16_t serial_rx, con return false; } reset(); - gps = new (std::nothrow) TinyGPSPlus(); + gps = new (std::nothrow) TinyGPSPlus(); easySerial = new (std::nothrow) ESPeasySerial(port, serial_rx, serial_tx); if (easySerial != nullptr) { diff --git a/src/src/PluginStructs/P111_data_struct.cpp b/src/src/PluginStructs/P111_data_struct.cpp index 913f4a77f9..7ff358b5f0 100644 --- a/src/src/PluginStructs/P111_data_struct.cpp +++ b/src/src/PluginStructs/P111_data_struct.cpp @@ -11,9 +11,20 @@ P111_data_struct::P111_data_struct(uint8_t csPin, uint8_t rstPin) : mfrc522(nullptr), _csPin(csPin), _rstPin(rstPin) {} +P111_data_struct::~P111_data_struct() { + if (mfrc522 != nullptr) { + delete mfrc522; + mfrc522 = nullptr; + } +} + void P111_data_struct::init() { - if (mfrc522 == nullptr){ - mfrc522 = new MFRC522 (_csPin, _rstPin); // Instantiate a MFRC522 + if (mfrc522 != nullptr) { + delete mfrc522; + mfrc522 = nullptr; + } + mfrc522 = new (std::nothrow) MFRC522(_csPin, _rstPin); // Instantiate a MFRC522 + if (mfrc522 != nullptr) { mfrc522->PCD_Init(); // Initialize MFRC522 reader } } diff --git a/src/src/PluginStructs/P111_data_struct.h b/src/src/PluginStructs/P111_data_struct.h index 45f6ed66ac..08eff6cbe3 100644 --- a/src/src/PluginStructs/P111_data_struct.h +++ b/src/src/PluginStructs/P111_data_struct.h @@ -9,11 +9,12 @@ struct P111_data_struct : public PluginTaskData_base { P111_data_struct(uint8_t csPin, uint8_t rstPin); + ~P111_data_struct(); void init(); uint8_t readCardStatus(unsigned long *key, bool *removedTag); String getCardName(); - MFRC522 *mfrc522; + MFRC522 *mfrc522 = nullptr; uint8_t counter = 0; From c993904f4d8e262548156acf93527fbe51e37ddd Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 23:58:22 +0100 Subject: [PATCH 010/100] [Webserver] Fix serving CSS I made an error in previous commit for this PR --- src/src/Helpers/ESPEasy_Storage.cpp | 2 +- src/src/Static/WebStaticData.cpp | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index e6fcdc8070..69e179414d 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -125,7 +125,7 @@ int fileSize(const String& fname) { const String patched_fname = patch_fname(fname); auto search = Cache.fileExistsMap.find(patched_fname); if (search != Cache.fileExistsMap.end()) { - return search->second >= 0; + return search->second; } int size = -1; if (ESPEASY_FS.exists(patched_fname)) { diff --git a/src/src/Static/WebStaticData.cpp b/src/src/Static/WebStaticData.cpp index 3281ff1e0d..37c39ac6f7 100644 --- a/src/src/Static/WebStaticData.cpp +++ b/src/src/Static/WebStaticData.cpp @@ -15,23 +15,28 @@ String generate_external_URL(const String& fname) { void serve_CSS() { - String url = F("esp.css"); - if (!fileExists(url)) + const String cssFile = F("esp.css"); + if (fileExists(cssFile)) { - #ifndef WEBSERVER_CSS - url = generate_external_URL(F("espeasy_default.css")); - #else addHtml(F("")); return; - #endif } + #ifndef WEBSERVER_CSS + addHtml(F("'); + #else addHtml(F("")); + #endif } void serve_favicon() { From dd774da16d650a41a70d7857964d5ea595ef6a10 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 6 Nov 2021 17:21:22 +0100 Subject: [PATCH 011/100] [TLS] Add ALPN protocol --- src/src/Helpers/ESPEasy_WiFiClientSecure.cpp | 11 ++++- src/src/Helpers/ESPEasy_WiFiClientSecure.h | 4 ++ src/src/Helpers/ESPEasy_ssl_client.cpp | 48 +++++++++++++------- src/src/Helpers/ESPEasy_ssl_client.h | 3 +- 4 files changed, 47 insertions(+), 19 deletions(-) diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp index f84c5aeb40..c08a7d22bd 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp @@ -41,6 +41,7 @@ ESPEasy_WiFiClientSecure::ESPEasy_WiFiClientSecure() _pskIdent = NULL; _psKey = NULL; next = NULL; + _alpn_protos = NULL; } @@ -64,6 +65,7 @@ ESPEasy_WiFiClientSecure::ESPEasy_WiFiClientSecure(int sock) _pskIdent = NULL; _psKey = NULL; next = NULL; + _alpn_protos = NULL; } ESPEasy_WiFiClientSecure::~ESPEasy_WiFiClientSecure() @@ -125,7 +127,7 @@ int ESPEasy_WiFiClientSecure::connect(const char *host, uint16_t port, const cha if(_timeout > 0){ sslclient->handshake_timeout = _timeout; } - int ret = start_ssl_client(sslclient, host, port, _timeout, CA_cert, cert, private_key, NULL, NULL, _use_insecure); + int ret = start_ssl_client(sslclient, host, port, _timeout, CA_cert, cert, private_key, NULL, NULL, _use_insecure, _alpn_protos); _lastError = ret; if (ret < 0) { log_e("start_ssl_client: %d", ret); @@ -145,7 +147,7 @@ int ESPEasy_WiFiClientSecure::connect(const char *host, uint16_t port, const cha if(_timeout > 0){ sslclient->handshake_timeout = _timeout; } - int ret = start_ssl_client(sslclient, host, port, _timeout, NULL, NULL, NULL, pskIdent, psKey, _use_insecure); + int ret = start_ssl_client(sslclient, host, port, _timeout, NULL, NULL, NULL, pskIdent, psKey, _use_insecure, _alpn_protos); _lastError = ret; if (ret < 0) { log_e("start_ssl_client: %d", ret); @@ -338,4 +340,9 @@ int ESPEasy_WiFiClientSecure::lastError(char *buf, const size_t size) void ESPEasy_WiFiClientSecure::setHandshakeTimeout(unsigned long handshake_timeout) { sslclient->handshake_timeout = handshake_timeout * 1000; +} + +void ESPEasy_WiFiClientSecure::setAlpnProtocols(const char **alpn_protos) +{ + _alpn_protos = alpn_protos; } \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.h b/src/src/Helpers/ESPEasy_WiFiClientSecure.h index 08e129446b..e219b37c40 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.h +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.h @@ -42,6 +42,7 @@ class ESPEasy_WiFiClientSecure : public WiFiClient const char *_private_key; const char *_pskIdent; // identity for PSK cipher suites const char *_psKey; // key in hex for PSK cipher suites + const char **_alpn_protos; public: ESPEasy_WiFiClientSecure *next; @@ -76,6 +77,9 @@ class ESPEasy_WiFiClientSecure : public WiFiClient bool loadPrivateKey(Stream& stream, size_t size); bool verify(const char* fingerprint, const char* domain_name); void setHandshakeTimeout(unsigned long handshake_timeout); + void setAlpnProtocols(const char **alpn_protos); + const mbedtls_x509_crt* getPeerCertificate() { return mbedtls_ssl_get_peer_cert(&sslclient->ssl_ctx); }; + bool getFingerprintSHA256(uint8_t sha256_result[32]) { return get_peer_fingerprint(sslclient, sha256_result); }; int setTimeout(uint32_t seconds){ return 0; } diff --git a/src/src/Helpers/ESPEasy_ssl_client.cpp b/src/src/Helpers/ESPEasy_ssl_client.cpp index baa7b67b1c..ebeabe99c5 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.cpp +++ b/src/src/Helpers/ESPEasy_ssl_client.cpp @@ -91,7 +91,7 @@ void ssl_init(ESPEasy_sslclient_context *ssl_client) } -int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure) +int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos) { char buf[512]; int ret, flags; @@ -160,6 +160,13 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui return handle_error(ret); } + if (alpn_protos != NULL) { + log_v("Setting ALPN protocols"); + if ((ret = mbedtls_ssl_conf_alpn_protocols(&ssl_client->ssl_conf, alpn_protos) ) != 0) { + return handle_error(ret); + } + } + // MBEDTLS_SSL_VERIFY_REQUIRED if a CA certificate is defined on Arduino IDE and // MBEDTLS_SSL_VERIFY_NONE if not. @@ -433,23 +440,10 @@ bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* f fingerprint_local[i] = low | (high << 4); } - // Get certificate provided by the peer - const mbedtls_x509_crt* crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx); - - if (!crt) - { - log_d("could not fetch peer certificate"); - return false; - } - // Calculate certificate's SHA256 fingerprint uint8_t fingerprint_remote[32]; - mbedtls_sha256_context sha256_ctx; - mbedtls_sha256_init(&sha256_ctx); - mbedtls_sha256_starts(&sha256_ctx, false); - mbedtls_sha256_update(&sha256_ctx, crt->raw.p, crt->raw.len); - mbedtls_sha256_finish(&sha256_ctx, fingerprint_remote); - mbedtls_sha256_free(&sha256_ctx); + if(!get_peer_fingerprint(ssl_client, fingerprint_remote)) + return false; // Check if fingerprints match if (memcmp(fingerprint_local, fingerprint_remote, 32)) @@ -465,6 +459,28 @@ bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* f return true; } +bool get_peer_fingerprint(ESPEasy_sslclient_context *ssl_client, uint8_t sha256[32]) +{ + if (!ssl_client) { + log_d("Invalid ssl_client pointer"); + return false; + }; + + const mbedtls_x509_crt* crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx); + if (!crt) { + log_d("Failed to get peer cert."); + return false; + }; + + mbedtls_sha256_context sha256_ctx; + mbedtls_sha256_init(&sha256_ctx); + mbedtls_sha256_starts(&sha256_ctx, false); + mbedtls_sha256_update(&sha256_ctx, crt->raw.p, crt->raw.len); + mbedtls_sha256_finish(&sha256_ctx, sha256); + + return true; +} + // Checks if peer certificate has specified domain in CN or SANs bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name) { diff --git a/src/src/Helpers/ESPEasy_ssl_client.h b/src/src/Helpers/ESPEasy_ssl_client.h index 9704b3ff80..de17f30709 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.h +++ b/src/src/Helpers/ESPEasy_ssl_client.h @@ -38,12 +38,13 @@ typedef struct ESPEasy_sslclient_context { void ssl_init(ESPEasy_sslclient_context *ssl_client); -int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure); +int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos); void stop_ssl_socket(ESPEasy_sslclient_context *ssl_client, const char *rootCABuff, const char *cli_cert, const char *cli_key); int data_to_read(ESPEasy_sslclient_context *ssl_client); int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, uint16_t len); int get_ssl_receive(ESPEasy_sslclient_context *ssl_client, uint8_t *data, int length); bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* fp, const char* domain_name); bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name); +bool get_peer_fingerprint(ESPEasy_sslclient_context *ssl_client, uint8_t sha256[32]); #endif \ No newline at end of file From eca630835c9a1d7bc4e70b6551586f29c974d011 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 6 Nov 2021 18:01:27 +0100 Subject: [PATCH 012/100] [TLS] Add fix for WiFiClientSecure connection timeout --- src/src/Helpers/ESPEasy_WiFiClientSecure.h | 2 +- src/src/Helpers/ESPEasy_ssl_client.cpp | 100 ++++++++++++++------- src/src/Helpers/ESPEasy_ssl_client.h | 3 +- 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.h b/src/src/Helpers/ESPEasy_WiFiClientSecure.h index e219b37c40..c228befd7a 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.h +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.h @@ -57,7 +57,7 @@ class ESPEasy_WiFiClientSecure : public WiFiClient int connect(const char *host, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key); int connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psKey); int connect(const char *host, uint16_t port, const char *pskIdent, const char *psKey); - int peek(); + int peek(); size_t write(uint8_t data); size_t write(const uint8_t *buf, size_t size); int available(); diff --git a/src/src/Helpers/ESPEasy_ssl_client.cpp b/src/src/Helpers/ESPEasy_ssl_client.cpp index ebeabe99c5..a867641a8c 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.cpp +++ b/src/src/Helpers/ESPEasy_ssl_client.cpp @@ -21,12 +21,12 @@ #include #ifndef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED -# error "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher" -#endif +# warning "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher" +#else const char *ESPEasy_pers = "esp32-tls"; -static int _handle_error(int err, const char * file, int line) +static int _handle_error(int err, const char * function, int line) { if(err == -30848){ return err; @@ -34,15 +34,16 @@ static int _handle_error(int err, const char * file, int line) #ifdef MBEDTLS_ERROR_C char error_buf[100]; mbedtls_strerror(err, error_buf, 100); - log_e("[%s():%d]: (%d) %s", file, line, err, error_buf); + log_e("[%s():%d]: (%d) %s", function, line, err, error_buf); #else - log_e("[%s():%d]: code %d", file, line, err); + log_e("[%s():%d]: code %d", function, line, err); #endif return err; } #define handle_error(e) _handle_error(e, __FUNCTION__, __LINE__) + ESPEasy_sslclient_context::ESPEasy_sslclient_context() { memset(&ssl_ctx, 0, sizeof(ssl_ctx)); @@ -116,30 +117,67 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui return -1; } + fcntl( ssl_client->socket, F_SETFL, fcntl( ssl_client->socket, F_GETFL, 0 ) | O_NONBLOCK ); struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = srv; serv_addr.sin_port = htons(port); - if (lwip_connect(ssl_client->socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == 0) { - if(timeout <= 0){ - timeout = 30000; // Milli seconds. - } - timeval so_timeout = { .tv_sec = timeout / 1000, .tv_usec = (timeout % 1000) * 1000 }; + if(timeout <= 0){ + timeout = 30000; // Milli seconds. + } -#define ROE(x,msg) { if (((x)<0)) { log_e("LWIP Socket config of " msg " failed."); return -1; }} - ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_RCVTIMEO, &so_timeout, sizeof(so_timeout)),"SO_RCVTIMEO"); - ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_SNDTIMEO, &so_timeout, sizeof(so_timeout)),"SO_SNDTIMEO"); + fd_set fdset; + struct timeval tv; + FD_ZERO(&fdset); + FD_SET(ssl_client->socket, &fdset); + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; - ROE(lwip_setsockopt(ssl_client->socket, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)),"TCP_NODELAY"); - ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)),"SO_KEEPALIVE"); - } else { - log_e("Connect to Server failed!"); + int res = lwip_connect(ssl_client->socket, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); + if (res < 0 && errno != EINPROGRESS) { + log_e("connect on fd %d, errno: %d, \"%s\"", ssl_client->socket, errno, strerror(errno)); + close(ssl_client->socket); return -1; } - fcntl( ssl_client->socket, F_SETFL, fcntl( ssl_client->socket, F_GETFL, 0 ) | O_NONBLOCK ); + res = select(ssl_client->socket + 1, nullptr, &fdset, nullptr, timeout<0 ? nullptr : &tv); + if (res < 0) { + log_e("select on fd %d, errno: %d, \"%s\"", ssl_client->socket, errno, strerror(errno)); + close(ssl_client->socket); + return -1; + } else if (res == 0) { + log_i("select returned due to timeout %d ms for fd %d", timeout, ssl_client->socket); + close(ssl_client->socket); + return -1; + } else { + int sockerr; + socklen_t len = (socklen_t)sizeof(int); + res = getsockopt(ssl_client->socket, SOL_SOCKET, SO_ERROR, &sockerr, &len); + + if (res < 0) { + log_e("getsockopt on fd %d, errno: %d, \"%s\"", ssl_client->socket, errno, strerror(errno)); + close(ssl_client->socket); + return -1; + } + + if (sockerr != 0) { + log_e("socket error on fd %d, errno: %d, \"%s\"", ssl_client->socket, sockerr, strerror(sockerr)); + close(ssl_client->socket); + return -1; + } + } + + +#define ROE(x,msg) { if (((x)<0)) { log_e("LWIP Socket config of " msg " failed."); return -1; }} + ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),"SO_RCVTIMEO"); + ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),"SO_SNDTIMEO"); + + ROE(lwip_setsockopt(ssl_client->socket, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)),"TCP_NODELAY"); + ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)),"SO_KEEPALIVE"); + + log_v("Seeding the random number generator"); mbedtls_entropy_free(&ssl_client->entropy_ctx); @@ -166,7 +204,7 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui return handle_error(ret); } } - + // MBEDTLS_SSL_VERIFY_REQUIRED if a CA certificate is defined on Arduino IDE and // MBEDTLS_SSL_VERIFY_NONE if not. @@ -223,12 +261,11 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui mbedtls_x509_crt_init(&ssl_client->client_cert); mbedtls_pk_init(&ssl_client->client_key); - log_v("Loading CRT cert"); ret = mbedtls_x509_crt_parse(&ssl_client->client_cert, (const unsigned char *)cli_cert, strlen(cli_cert) + 1); if (ret < 0) { - // free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash. + // free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash. ssl_client->free_client_cert(); return handle_error(ret); } @@ -275,9 +312,9 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui ssl_client->free_ca_cert(); ssl_client->free_client_cert(); // ++++++++++ END ++++++++++ - return -1; + return -1; } - vTaskDelay(2);//2 ticks + vTaskDelay(2);//2 ticks } @@ -351,16 +388,18 @@ int data_to_read(ESPEasy_sslclient_context *ssl_client) return res; } -int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, uint16_t len) +int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, size_t len) { log_v("Writing HTTP request with %d bytes...", len); //for low level debug int ret = -1; - if ((ret = mbedtls_ssl_write(&ssl_client->ssl_ctx, data, len)) <= 0){ - log_v("Handling error %d", ret); //for low level debug - return handle_error(ret); - } else{ - log_v("Returning with %d bytes written", ret); //for low level debug + while ((ret = mbedtls_ssl_write(&ssl_client->ssl_ctx, data, len)) <= 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) { + log_v("Handling error %d", ret); //for low level debug + return handle_error(ret); + } + //wait for space to become available + vTaskDelay(2); } return ret; @@ -527,4 +566,5 @@ bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_nam } return false; -} \ No newline at end of file +} +#endif diff --git a/src/src/Helpers/ESPEasy_ssl_client.h b/src/src/Helpers/ESPEasy_ssl_client.h index de17f30709..86560b0faa 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.h +++ b/src/src/Helpers/ESPEasy_ssl_client.h @@ -41,10 +41,9 @@ void ssl_init(ESPEasy_sslclient_context *ssl_client); int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos); void stop_ssl_socket(ESPEasy_sslclient_context *ssl_client, const char *rootCABuff, const char *cli_cert, const char *cli_key); int data_to_read(ESPEasy_sslclient_context *ssl_client); -int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, uint16_t len); +int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, size_t len); int get_ssl_receive(ESPEasy_sslclient_context *ssl_client, uint8_t *data, int length); bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* fp, const char* domain_name); bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name); bool get_peer_fingerprint(ESPEasy_sslclient_context *ssl_client, uint8_t sha256[32]); - #endif \ No newline at end of file From c909eaa22c39cb520b3e7127c8d9ab35c05ff52b Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 6 Nov 2021 21:52:29 +0100 Subject: [PATCH 013/100] [MQTT TLS]Display connection info on controller page --- src/src/ESPEasyCore/Controller.cpp | 15 ++++++++- src/src/Helpers/ESPEasy_WiFiClientSecure.cpp | 22 ++++++++++++- src/src/Helpers/ESPEasy_WiFiClientSecure.h | 4 +++ src/src/Helpers/ESPEasy_ssl_client.cpp | 3 ++ src/src/Helpers/ESPEasy_ssl_client.h | 3 +- src/src/WebServer/ControllerPage.cpp | 33 ++++++++++++++++++++ 6 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 3728f2d195..1335380de0 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -204,7 +204,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) uint16_t mqttPort = ControllerSettings.Port; #ifdef USE_MQTT_TLS - mqtt_tls_last_errorstr = EMPTY_STRING; + mqtt_tls_last_errorstr.clear(); mqtt_tls_last_error = 0; const TLS_types TLS_type = ControllerSettings.TLStype(); if (TLS_type != TLS_types::NoTLS && nullptr == mqtt_tls) { @@ -279,6 +279,8 @@ bool MQTTConnect(controllerIndex_t controller_idx) } } if (TLS_type != TLS_types::NoTLS) { + // Certificate expiry not enabled in Mbed TLS. +// mqtt_tls->setX509Time(node_time.getUnixTime()); mqtt_tls->setTimeout(ControllerSettings.ClientTimeout); #ifdef ESP8266 mqtt_tls->setBufferSizes(1024,1024); @@ -382,6 +384,17 @@ bool MQTTConnect(controllerIndex_t controller_idx) log += clientid; addLog(LOG_LEVEL_INFO, log); + + #ifdef USE_MQTT_TLS + #ifdef ESP32 + { + log = F("MQTT : Peer certificate info: "); + log += mqtt_tls->getPeerCertificateInfo(); + addLog(LOG_LEVEL_INFO, log); + log.clear(); + } + #endif + #endif String subscribeTo = ControllerSettings.Subscribe; parseSystemVariables(subscribeTo, false); diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp index c08a7d22bd..b9ee2e5e43 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp @@ -17,6 +17,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef ESP32 #include #include #include @@ -345,4 +346,23 @@ void ESPEasy_WiFiClientSecure::setHandshakeTimeout(unsigned long handshake_timeo void ESPEasy_WiFiClientSecure::setAlpnProtocols(const char **alpn_protos) { _alpn_protos = alpn_protos; -} \ No newline at end of file +} + +String ESPEasy_WiFiClientSecure::getPeerCertificateInfo() +{ + const mbedtls_x509_crt* peer = getPeerCertificate(); + String res; + if (peer != nullptr) { + char buf[1024] = {0}; + int l = mbedtls_x509_crt_info (buf, sizeof(buf), "", peer); + if (l > 0) { + if (res.reserve(l)) { + for (int i = 0; i < l; ++i) { + res += buf[i]; + } + } + } + } + return res; +} +#endif \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.h b/src/src/Helpers/ESPEasy_WiFiClientSecure.h index c228befd7a..0ddaffc20f 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.h +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.h @@ -21,6 +21,8 @@ for memory leaks not yet present in the "older" core versions. */ +#ifdef ESP32 + #ifndef ESPEASY_WiFiClientSecure_h #define ESPEASY_WiFiClientSecure_h #include @@ -78,6 +80,7 @@ class ESPEasy_WiFiClientSecure : public WiFiClient bool verify(const char* fingerprint, const char* domain_name); void setHandshakeTimeout(unsigned long handshake_timeout); void setAlpnProtocols(const char **alpn_protos); + String getPeerCertificateInfo(); const mbedtls_x509_crt* getPeerCertificate() { return mbedtls_ssl_get_peer_cert(&sslclient->ssl_ctx); }; bool getFingerprintSHA256(uint8_t sha256_result[32]) { return get_peer_fingerprint(sslclient, sha256_result); }; @@ -114,4 +117,5 @@ class ESPEasy_WiFiClientSecure : public WiFiClient using Print::write; }; +#endif #endif \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_ssl_client.cpp b/src/src/Helpers/ESPEasy_ssl_client.cpp index a867641a8c..0cb0394e6d 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.cpp +++ b/src/src/Helpers/ESPEasy_ssl_client.cpp @@ -8,6 +8,8 @@ * Additions Copyright (C) 2017 Evandro Luis Copercini, Apache 2.0 License. */ +#ifdef ESP32 + #include #include #include @@ -568,3 +570,4 @@ bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_nam return false; } #endif +#endif \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_ssl_client.h b/src/src/Helpers/ESPEasy_ssl_client.h index 86560b0faa..5c612c3ae7 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.h +++ b/src/src/Helpers/ESPEasy_ssl_client.h @@ -1,7 +1,7 @@ /* Provide SSL/TLS functions to ESP32 with Arduino IDE * by Evandro Copercini - 2017 - Apache 2.0 License */ - +#ifdef ESP32 #ifndef ESPEASY_ARD_SSL_H #define ESPEASY_ARD_SSL_H #include @@ -46,4 +46,5 @@ int get_ssl_receive(ESPEasy_sslclient_context *ssl_client, uint8_t *data, int le bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* fp, const char* domain_name); bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name); bool get_peer_fingerprint(ESPEasy_sslclient_context *ssl_client, uint8_t sha256[32]); +#endif #endif \ No newline at end of file diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 01934f7132..6980cc9a65 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -17,6 +17,10 @@ #include "../Globals/Protocol.h" #include "../Globals/Settings.h" +#ifdef USES_MQTT +#include "../Globals/MQTT.h" +#endif + #include "../Helpers/_CPlugin_Helper_webform.h" #include "../Helpers/_Plugin_SensorTypeHelper.h" #include "../Helpers/ESPEasy_Storage.h" @@ -417,6 +421,35 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addHtmlError(F("Bug in CPlugin::Function::CPLUGIN_WEBFORM_LOAD, should not append to string, use addHtml() instead")); } } + { + #ifdef USES_MQTT + if (Protocol[ProtocolIndex].usesMQTT) { + addFormSubHeader(F("Connection Info")); + addRowLabel(F("MQTT Client Connected")); + addEnabled(MQTTclient_connected); + +#ifdef USE_MQTT_TLS + if (Protocol[ProtocolIndex].usesTLS) { + addRowLabel(F("Last Error")); + addHtmlInt(mqtt_tls_last_error); + addHtml(F(": ")); + addHtml(mqtt_tls_last_errorstr); + + #ifdef ESP32 + if (MQTTclient_connected) { + addRowLabel(F("Peer Certificate")); + String peerInfo = mqtt_tls->getPeerCertificateInfo(); + peerInfo.replace(F("\n"), F("
")); + addTextBox(F("peer_cert"), peerInfo, peerInfo.length(), true); + } + #endif + + } +#endif + } + #endif + } + // Separate enabled checkbox as it doesn't need to use the ControllerSettings. // So ControllerSettings object can be destructed before controller specific settings are loaded. addControllerEnabledForm(controllerindex); From fc22043edbae52add8b986f66e0b2058687c2397 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 6 Nov 2021 21:53:26 +0100 Subject: [PATCH 014/100] [Cleanup] Use .clear() on strings instead of assigning empty string --- src/_C015.ino | 2 +- src/_P016_IR.ino | 8 ++++---- src/_P050_TCS34725.ino | 4 ++-- src/_P073_7DGT.ino | 2 +- src/src/ESPEasyCore/ESPEasyRules.cpp | 4 ++-- src/src/PluginStructs/P104_data_struct.cpp | 6 +++--- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/_C015.ino b/src/_C015.ino index 4379528472..6821f547d8 100644 --- a/src/_C015.ino +++ b/src/_C015.ino @@ -194,7 +194,7 @@ bool CPlugin_015(CPlugin::Function function, struct EventStruct *event, String& if (!isvalid) { // send empty string to Blynk in case of error - formattedValue = EMPTY_STRING; + formattedValue.clear(); } String valueName = ExtraTaskSettings.TaskDeviceValueNames[x]; diff --git a/src/_P016_IR.ino b/src/_P016_IR.ino index f2229bc8d9..70a11c632e 100644 --- a/src/_P016_IR.ino +++ b/src/_P016_IR.ino @@ -378,7 +378,7 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) html_TD(); addCheckBox(getPluginCustomArgName(rowCnt + 1), bitRead(P016_data->CommandLines[varNr].CodeFlags, P16_FLAGS_REPEAT)); html_TD(); - strCode = EMPTY_STRING; + strCode.clear(); if (P016_data->CommandLines[varNr].Code > 0) { strCode = uint64ToString(P016_data->CommandLines[varNr].Code, 16); // convert code to hex for display @@ -393,7 +393,7 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) html_TD(); addCheckBox(getPluginCustomArgName(rowCnt + 4), bitRead(P016_data->CommandLines[varNr].AlternativeCodeFlags, P16_FLAGS_REPEAT)); html_TD(); - strCode = EMPTY_STRING; + strCode.clear(); if (P016_data->CommandLines[varNr].AlternativeCode > 0) { strCode = uint64ToString(P016_data->CommandLines[varNr].AlternativeCode, 16); // convert code to hex for display @@ -470,7 +470,7 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) for (uint8_t varNr = 0; varNr < P16_Nlines; varNr++) { P016_data->CommandLines.push_back(tCommandLinesV2()); - strError = EMPTY_STRING; + strError.clear(); // Normal Code & flags P016_data->CommandLines[varNr].CodeDecodeType = static_cast(getFormItemInt(getPluginCustomArgName(rowCnt + 0))); @@ -754,7 +754,7 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) if (state.clock >= 0) { doc[F("clock")] = state.clock; // Nr. of mins past midnight to set the clock to. (< 0 means off.) } - output = EMPTY_STRING; + output.clear(); serializeJson(doc, output); event->String2 = output; diff --git a/src/_P050_TCS34725.ino b/src/_P050_TCS34725.ino index 73219d028b..ba4a3cd794 100644 --- a/src/_P050_TCS34725.ino +++ b/src/_P050_TCS34725.ino @@ -430,7 +430,7 @@ boolean Plugin_050(uint8_t function, struct EventStruct *event, String& string) RuleEvent += String(static_cast(b) / t * sRGBFactor, 4); break; default: - RuleEvent = EMPTY_STRING; + RuleEvent.clear(); break; } if (!RuleEvent.isEmpty()) { @@ -466,7 +466,7 @@ boolean Plugin_050(uint8_t function, struct EventStruct *event, String& string) RuleEvent += c; break; default: - RuleEvent = EMPTY_STRING; + RuleEvent.clear(); break; } if (!RuleEvent.isEmpty()) { diff --git a/src/_P073_7DGT.ino b/src/_P073_7DGT.ino index 14ed3d6de4..91946539ae 100644 --- a/src/_P073_7DGT.ino +++ b/src/_P073_7DGT.ino @@ -401,7 +401,7 @@ struct P073_data_struct : public PluginTaskData_base { } void setTextToScroll(const String& text) { - _textToScroll = EMPTY_STRING; + _textToScroll.clear(); if (text.length() > 0) { int bufToFill = getBufferLength(displayModel); diff --git a/src/src/ESPEasyCore/ESPEasyRules.cpp b/src/src/ESPEasyCore/ESPEasyRules.cpp index 7d0c1d81cd..2750262741 100644 --- a/src/src/ESPEasyCore/ESPEasyRules.cpp +++ b/src/src/ESPEasyCore/ESPEasyRules.cpp @@ -252,7 +252,7 @@ String rulesProcessingFile(const String& fileName, const String& event) { } // Prepare for new line - line = EMPTY_STRING; + line.clear(); line.reserve(longestLineSize); firstNonSpaceRead = false; commentFound = false; @@ -743,7 +743,7 @@ void parseCompleteNonCommentLine(String& line, const String& event, String eventTrigger; - action = EMPTY_STRING; + action.clear(); if (!codeBlock) // do not check "on" rules if a block of actions is to be // processed diff --git a/src/src/PluginStructs/P104_data_struct.cpp b/src/src/PluginStructs/P104_data_struct.cpp index effa3775b5..0ea77e8444 100644 --- a/src/src/PluginStructs/P104_data_struct.cpp +++ b/src/src/PluginStructs/P104_data_struct.cpp @@ -334,7 +334,7 @@ void P104_data_struct::loadSettings() { zones.push_back(P104_zone_struct(zoneIndex + 1)); if (zones[zoneIndex].text == F("\"\"")) { // Special case - zones[zoneIndex].text = EMPTY_STRING; + zones[zoneIndex].text.clear(); } zoneIndex++; @@ -1565,7 +1565,7 @@ String P104_data_struct::enquoteString(const String& input) { * saveSettings gather the zones data from the UI and store in customsettings **************************************/ bool P104_data_struct::saveSettings() { - error = EMPTY_STRING; // Clear + error.clear(); // Clear String zbuffer; # ifdef P104_DEBUG_DEV @@ -1680,7 +1680,7 @@ bool P104_data_struct::saveSettings() { if (zbuffer.reserve(P104_SETTINGS_BUFFER_V2 + 2)) { for (auto it = zones.begin(); it != zones.end() && error.length() == 0; ++it) { - zbuffer = EMPTY_STRING; + zbuffer.clear(); // WARNING: Order of values should match the numeric order of P104_OFFSET_* values zbuffer += it->size; // 2 From c8d56bea50dbdbf3111a64196dffdb38cfca481c Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 7 Nov 2021 01:05:32 +0100 Subject: [PATCH 015/100] [TLS] Making proper datastructure init --- src/src/Helpers/ESPEasy_ssl_client.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/src/Helpers/ESPEasy_ssl_client.cpp b/src/src/Helpers/ESPEasy_ssl_client.cpp index 0cb0394e6d..bf8ff5dbd6 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.cpp +++ b/src/src/Helpers/ESPEasy_ssl_client.cpp @@ -48,13 +48,14 @@ static int _handle_error(int err, const char * function, int line) ESPEasy_sslclient_context::ESPEasy_sslclient_context() { - memset(&ssl_ctx, 0, sizeof(ssl_ctx)); - memset(&ssl_conf, 0, sizeof(ssl_conf)); - memset(&drbg_ctx, 0, sizeof(drbg_ctx)); - memset(&entropy_ctx, 0, sizeof(entropy_ctx)); - memset(&ca_cert, 0, sizeof(ca_cert)); - memset(&client_cert, 0, sizeof(client_cert)); - memset(&client_key, 0, sizeof(client_key)); + mbedtls_ssl_init(&ssl_ctx); + mbedtls_ssl_config_init(&ssl_conf); + mbedtls_ctr_drbg_init(&drbg_ctx); + + mbedtls_entropy_init(&entropy_ctx); + mbedtls_x509_crt_init(&ca_cert); + mbedtls_x509_crt_init(&client_cert); + mbedtls_pk_init(&client_key); } From d801c36c2a132a35c07658d6f3672edd8e320923 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 7 Nov 2021 01:06:15 +0100 Subject: [PATCH 016/100] [MQTT TLS] Improve controller setup page show peer certificate --- src/src/WebServer/ControllerPage.cpp | 39 ++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 6980cc9a65..3e154427c0 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -422,9 +422,9 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex } } { - #ifdef USES_MQTT +#ifdef USES_MQTT if (Protocol[ProtocolIndex].usesMQTT) { - addFormSubHeader(F("Connection Info")); + addFormSubHeader(F("Connection Status")); addRowLabel(F("MQTT Client Connected")); addEnabled(MQTTclient_connected); @@ -436,23 +436,46 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addHtml(mqtt_tls_last_errorstr); #ifdef ESP32 - if (MQTTclient_connected) { - addRowLabel(F("Peer Certificate")); - String peerInfo = mqtt_tls->getPeerCertificateInfo(); - peerInfo.replace(F("\n"), F("
")); - addTextBox(F("peer_cert"), peerInfo, peerInfo.length(), true); + if (MQTTclient_connected && mqtt_tls != nullptr) { + addFormSubHeader(F("Peer Certificate")); + + { + addRowLabel(F("Certificate Info")); + addHtml(F("")); + } + { + uint8_t sha256_result[32] = {0}; + if (mqtt_tls->getFingerprintSHA256(sha256_result)) { + String fingerprint; + fingerprint.reserve(64); + for (size_t i = 0; i < 32; ++i) { + fingerprint += String(sha256_result[i], HEX); + } + fingerprint.toLowerCase(); + addFormTextBox(F("Certificate Fingerprint"), + F("fingerprint"), + fingerprint, + 64, + true); // ReadOnly + } + } + + } #endif } #endif } - #endif +#endif } // Separate enabled checkbox as it doesn't need to use the ControllerSettings. // So ControllerSettings object can be destructed before controller specific settings are loaded. addControllerEnabledForm(controllerindex); + } addFormSeparator(2); From 8af28b820b818e3bfb34d46e54fa8ea2768c5d3c Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 25 Nov 2021 01:46:05 +0100 Subject: [PATCH 017/100] [MQTT TLS] Add certificate fingerprint validation --- .../DataStructs/ControllerSettingsStruct.cpp | 3 + .../DataStructs/ControllerSettingsStruct.h | 1 + src/src/DataStructs/Web_StreamingBuffer.cpp | 5 +- src/src/DataTypes/TLS_types.cpp | 1 + src/src/DataTypes/TLS_types.h | 1 + src/src/ESPEasyCore/Controller.cpp | 88 +++++++++++++++++- src/src/ESPEasyCore/Controller.h | 6 ++ src/src/Globals/MQTT.cpp | 1 + src/src/Globals/MQTT.h | 2 + src/src/Helpers/ESPEasy_Storage.cpp | 90 ++++++++++--------- src/src/Helpers/ESPEasy_Storage.h | 2 +- src/src/Helpers/ESPEasy_WiFiClientSecure.h | 1 + src/src/Helpers/_CPlugin_Helper_webform.cpp | 69 ++++++++++---- src/src/WebServer/ControllerPage.cpp | 19 ++-- src/src/WebServer/SysInfoPage.cpp | 17 ++-- src/src/WebServer/WebServer.cpp | 26 ++++-- src/src/WebServer/WebServer.h | 12 +-- 17 files changed, 248 insertions(+), 96 deletions(-) diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index 0a272123b1..b5252c4257 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -349,6 +349,9 @@ String ControllerSettingsStruct::getCertificateFilename() const case TLS_types::TLS_CA_CERT: certFile += F(".cacert"); break; + case TLS_types::TLS_FINGERPRINT: + certFile += F(".fp"); + break; } return certFile; diff --git a/src/src/DataStructs/ControllerSettingsStruct.h b/src/src/DataStructs/ControllerSettingsStruct.h index 73aff6dc70..e5d529f6a7 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.h +++ b/src/src/DataStructs/ControllerSettingsStruct.h @@ -65,6 +65,7 @@ struct ControllerSettingsStruct CONTROLLER_IP, CONTROLLER_PORT, CONTROLLER_MQTT_TLS_TYPE, + CONTROLLER_MQTT_TLS_STORE_FINGERPRINT, CONTROLLER_USER, CONTROLLER_PASS, CONTROLLER_MIN_SEND_INTERVAL, diff --git a/src/src/DataStructs/Web_StreamingBuffer.cpp b/src/src/DataStructs/Web_StreamingBuffer.cpp index 4898333e2d..114b05afa8 100644 --- a/src/src/DataStructs/Web_StreamingBuffer.cpp +++ b/src/src/DataStructs/Web_StreamingBuffer.cpp @@ -11,8 +11,11 @@ #include "../Helpers/ESPEasy_time_calc.h" - +#ifdef ESP32 +#define CHUNKED_BUFFER_SIZE 1500 +#else #define CHUNKED_BUFFER_SIZE 400 +#endif Web_StreamingBuffer::Web_StreamingBuffer(void) : lowMemorySkip(false), initialRam(0), beforeTXRam(0), duringTXRam(0), finalRam(0), maxCoreUsage(0), diff --git a/src/src/DataTypes/TLS_types.cpp b/src/src/DataTypes/TLS_types.cpp index 5eaf38b4b1..805d8bc5a3 100644 --- a/src/src/DataTypes/TLS_types.cpp +++ b/src/src/DataTypes/TLS_types.cpp @@ -7,6 +7,7 @@ const __FlashStringHelper* toString(TLS_types tls_type) case TLS_types::TLS_PSK: return F("TLS PreSharedKey"); case TLS_types::TLS_CA_CERT: return F("TLS CA Cert"); case TLS_types::TLS_insecure: return F("TLS No Checks (insecure)"); + case TLS_types::TLS_FINGERPRINT: return F("TLS Certficate Fingerprint"); } return F("No TLS"); } diff --git a/src/src/DataTypes/TLS_types.h b/src/src/DataTypes/TLS_types.h index a8aec9aa14..3cf81fd398 100644 --- a/src/src/DataTypes/TLS_types.h +++ b/src/src/DataTypes/TLS_types.h @@ -11,6 +11,7 @@ enum class TLS_types { TLS_PSK = 1, // Pre-Shared-Key TLS_CA_CERT = 2, // Validate server certificate against known CA //TLS_CA_CLI_CERT = 3, // TLS_CA_CERT + supply client certificate for authentication + TLS_FINGERPRINT = 4, // Use certificate fingerprint TLS_insecure = 0xF // Set as last option, do not check supplied certificate. Ideal for man-in-the-middle attack. }; diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 1335380de0..ee15b26e21 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -217,7 +217,8 @@ bool MQTTConnect(controllerIndex_t controller_idx) mqtt_rootCA.clear(); if (mqtt_tls == nullptr) { - addLog(LOG_LEVEL_ERROR, F("MQTT : Could not create TLS client, out of memory")); + mqtt_tls_last_errorstr = F("MQTT : Could not create TLS client, out of memory"); + addLog(LOG_LEVEL_ERROR, mqtt_tls_last_errorstr); return false; } } @@ -254,6 +255,12 @@ bool MQTTConnect(controllerIndex_t controller_idx) if (mqtt_rootCA.isEmpty()) { LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_rootCA); + if (mqtt_rootCA.isEmpty()) { + // Fingerprint must be of some minimal length to continue. + mqtt_tls_last_errorstr = F("MQTT : No TLS root CA"); + addLog(LOG_LEVEL_ERROR, mqtt_tls_last_errorstr); + return false; + } #ifdef ESP32 mqtt_tls->setCACert(mqtt_rootCA.c_str()); @@ -272,8 +279,24 @@ bool MQTTConnect(controllerIndex_t controller_idx) break; } */ + case TLS_types::TLS_FINGERPRINT: + { + // Fingerprint is checked when making the connection. + mqtt_rootCA.clear(); + mqtt_fingerprint.clear(); + LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_fingerprint, false); + if (mqtt_fingerprint.length() < 32) { + // Fingerprint must be of some minimal length to continue. + mqtt_tls_last_errorstr = F("MQTT : Stored TLS fingerprint too small"); + addLog(LOG_LEVEL_ERROR, mqtt_tls_last_errorstr); + return false; + } + mqtt_tls->setInsecure(); + break; + } case TLS_types::TLS_insecure: { + mqtt_rootCA.clear(); mqtt_tls->setInsecure(); break; } @@ -355,9 +378,41 @@ bool MQTTConnect(controllerIndex_t controller_idx) #endif #ifdef ESP32 mqtt_tls_last_error = mqtt_tls->lastError(buf,128); + mqtt_tls->clearLastError(); #endif mqtt_tls_last_errorstr = buf; } + if (TLS_type == TLS_types::TLS_FINGERPRINT) + { + // Check fingerprint + if (MQTTresult) { + const int newlinepos = mqtt_fingerprint.indexOf('\n'); + String fp; + String dn; + if (ControllerSettings.UseDNS) dn = ControllerSettings.getHost(); + if (newlinepos == -1) { + fp = mqtt_fingerprint; + } else { + fp = mqtt_fingerprint.substring(0, newlinepos); + const int newlinepos2 = mqtt_fingerprint.indexOf('\n', newlinepos); + if (newlinepos2 == -1) + dn = mqtt_fingerprint.substring(newlinepos + 1); + else + dn = mqtt_fingerprint.substring(newlinepos + 1, newlinepos2); + dn.trim(); + + } + if (!mqtt_tls->verify( + fp.c_str(), + dn.isEmpty() ? nullptr : dn.c_str())) + { + mqtt_tls_last_errorstr += F("TLS Fingerprint does not match"); + addLog(LOG_LEVEL_INFO, mqtt_fingerprint); + MQTTresult = false; + } + } + } + #endif @@ -715,6 +770,37 @@ void MQTTStatus(struct EventStruct *event, const String& status) } } + +#ifdef USE_MQTT_TLS +bool GetTLSfingerprint(String& fp) +{ + #ifdef ESP32 + if (MQTTclient_connected && mqtt_tls != nullptr) { + uint8_t sha256_result[32] = {0}; + if (mqtt_tls->getFingerprintSHA256(sha256_result)) { + fp.reserve(64); + for (size_t i = 0; i < 32; ++i) { + const String tmp(sha256_result[i], HEX); + switch (tmp.length()) { + case 0: + fp += '0'; + // fall through + case 1: + fp += '0'; + break; + } + fp += tmp; + } + fp.toLowerCase(); + return true; + } + } + #endif + return false; +} + +#endif + #endif // USES_MQTT diff --git a/src/src/ESPEasyCore/Controller.h b/src/src/ESPEasyCore/Controller.h index 0a69b48a4f..e14ac48748 100644 --- a/src/src/ESPEasyCore/Controller.h +++ b/src/src/ESPEasyCore/Controller.h @@ -69,6 +69,12 @@ bool MQTTpublish(controllerIndex_t controller_idx, taskIndex_t taskIndex, Strin * Send status info back to channel where request came from \*********************************************************************************************/ void MQTTStatus(struct EventStruct *event, const String& status); + +#ifdef USE_MQTT_TLS +bool GetTLSfingerprint(String& fp); + +#endif + #endif //USES_MQTT diff --git a/src/src/Globals/MQTT.cpp b/src/src/Globals/MQTT.cpp index d859f7b138..23eb7dd410 100644 --- a/src/src/Globals/MQTT.cpp +++ b/src/src/Globals/MQTT.cpp @@ -16,6 +16,7 @@ BearSSL::WiFiClientSecure* mqtt_tls; BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 String mqtt_rootCA; +String mqtt_fingerprint; # endif // ifdef USE_MQTT_TLS PubSubClient MQTTclient(mqtt); diff --git a/src/src/Globals/MQTT.h b/src/src/Globals/MQTT.h index 3cf7ff14b3..d73894f02b 100644 --- a/src/src/Globals/MQTT.h +++ b/src/src/Globals/MQTT.h @@ -33,6 +33,8 @@ extern BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 extern String mqtt_rootCA; +extern String mqtt_fingerprint; + # endif // ifdef USE_MQTT_TLS extern PubSubClient MQTTclient; extern bool MQTTclient_should_reconnect; diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 69e179414d..b2a46b1af7 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -1070,7 +1070,7 @@ String SaveCertificate(const String& fname, const String& certificate) return SaveToFile(fname.c_str(), 0, (const uint8_t *)certificate.c_str(), certificate.length() + 1); } -String LoadCertificate(const String& fname, String& certificate) +String LoadCertificate(const String& fname, String& certificate, bool cleanup) { bool changed = false; if (fileExists(fname)) { @@ -1104,15 +1104,17 @@ String LoadCertificate(const String& fname, String& certificate) } f.close(); - if (!cleanupCertificate(certificate, changed)) { - certificate.clear(); - #ifndef BUILD_NO_DEBUG - log += F(" ERROR, Invalid certificate format"); - #endif - addLog(LOG_LEVEL_ERROR, log); - return log; - } else if (changed) { - //return SaveCertificate(fname, certificate); + if (cleanup) { + if (!cleanupCertificate(certificate, changed)) { + certificate.clear(); + #ifndef BUILD_NO_DEBUG + log += F(" ERROR, Invalid certificate format"); + #endif + addLog(LOG_LEVEL_ERROR, log); + return log; + } else if (changed) { + //return SaveCertificate(fname, certificate); + } } } @@ -1532,56 +1534,60 @@ size_t SpiffsUsedBytes() { } size_t SpiffsTotalBytes() { - size_t result = 1; // Do not output 0, this may be used in divisions. - - #ifdef ESP32 - result = ESPEASY_FS.totalBytes(); - #endif // ifdef ESP32 - #ifdef ESP8266 - fs::FSInfo fs_info; - ESPEASY_FS.info(fs_info); - result = fs_info.totalBytes; - #endif // ifdef ESP8266 + static size_t result = 1; // Do not output 0, this may be used in divisions. + if (result == 1) { + #ifdef ESP32 + result = ESPEASY_FS.totalBytes(); + #endif // ifdef ESP32 + #ifdef ESP8266 + fs::FSInfo fs_info; + ESPEASY_FS.info(fs_info); + result = fs_info.totalBytes; + #endif // ifdef ESP8266 + } return result; } size_t SpiffsBlocksize() { - size_t result = 8192; // Some default viable for most 1 MB file systems - - #ifdef ESP32 - result = 8192; // Just assume 8k, since we cannot query it - #endif // ifdef ESP32 - #ifdef ESP8266 - fs::FSInfo fs_info; - ESPEASY_FS.info(fs_info); - result = fs_info.blockSize; - #endif // ifdef ESP8266 + static size_t result = 1; + if (result == 1) { + #ifdef ESP32 + result = 8192; // Just assume 8k, since we cannot query it + #endif // ifdef ESP32 + #ifdef ESP8266 + fs::FSInfo fs_info; + ESPEASY_FS.info(fs_info); + result = fs_info.blockSize; + #endif // ifdef ESP8266 + } return result; } size_t SpiffsPagesize() { - size_t result = 256; // Most common - - #ifdef ESP32 - result = 256; // Just assume 256, since we cannot query it - #endif // ifdef ESP32 - #ifdef ESP8266 - fs::FSInfo fs_info; - ESPEASY_FS.info(fs_info); - result = fs_info.pageSize; - #endif // ifdef ESP8266 + static size_t result = 1; + if (result == 1) { + #ifdef ESP32 + result = 256; // Just assume 256, since we cannot query it + #endif // ifdef ESP32 + #ifdef ESP8266 + fs::FSInfo fs_info; + ESPEASY_FS.info(fs_info); + result = fs_info.pageSize; + #endif // ifdef ESP8266 + } return result; } size_t SpiffsFreeSpace() { int freeSpace = SpiffsTotalBytes() - SpiffsUsedBytes(); + const size_t blocksize = SpiffsBlocksize(); - if (freeSpace < static_cast(2 * SpiffsBlocksize())) { + if (freeSpace < static_cast(2 * blocksize)) { // Not enough free space left to store anything // There needs to be minimum of 2 free blocks. return 0; } - return freeSpace - 2 * SpiffsBlocksize(); + return freeSpace - 2 * blocksize; } bool SpiffsFull() { diff --git a/src/src/Helpers/ESPEasy_Storage.h b/src/src/Helpers/ESPEasy_Storage.h index 620c041bbe..e450c7917b 100644 --- a/src/src/Helpers/ESPEasy_Storage.h +++ b/src/src/Helpers/ESPEasy_Storage.h @@ -177,7 +177,7 @@ String LoadNotificationSettings(int NotificationIndex, uint8_t *memAddress, int The content will be stripped from unusable character like quotes, spaces etc. \*********************************************************************************************/ String SaveCertificate(const String& fname, const String& certificate); -String LoadCertificate(const String& fname, String& certificate); +String LoadCertificate(const String& fname, String& certificate, bool cleanup = true); /********************************************************************************************\ diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.h b/src/src/Helpers/ESPEasy_WiFiClientSecure.h index 0ddaffc20f..b5241bfda5 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.h +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.h @@ -69,6 +69,7 @@ class ESPEasy_WiFiClientSecure : public WiFiClient void stop(); uint8_t connected(); int lastError(char *buf, const size_t size); + void clearLastError() { _lastError = 0; } void setInsecure(); // Don't validate the chain, just accept whatever is given. VERY INSECURE! void setPreSharedKey(const char *pskIdent, const char *psKey); // psKey in Hex void setCACert(const char *rootCA); diff --git a/src/src/Helpers/_CPlugin_Helper_webform.cpp b/src/src/Helpers/_CPlugin_Helper_webform.cpp index 7c89a385c8..8fe2075fdf 100644 --- a/src/src/Helpers/_CPlugin_Helper_webform.cpp +++ b/src/src/Helpers/_CPlugin_Helper_webform.cpp @@ -21,7 +21,8 @@ const __FlashStringHelper * toString(ControllerSettingsStruct::VarType parameter case ControllerSettingsStruct::CONTROLLER_HOSTNAME: return F("Controller Hostname"); case ControllerSettingsStruct::CONTROLLER_IP: return F("Controller IP"); case ControllerSettingsStruct::CONTROLLER_PORT: return F("Controller Port"); - case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: return F("Use TLS"); + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: return F("Use TLS"); + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT: return F("Store Fingerprint"); case ControllerSettingsStruct::CONTROLLER_USER: return F("Controller User"); case ControllerSettingsStruct::CONTROLLER_PASS: return F("Controller Password"); @@ -118,6 +119,26 @@ void addControllerEnabledForm(controllerIndex_t controllerindex) { addFormCheckBox(displayName, internalName, Settings.ControllerEnabled[controllerindex]); } +void addCertificateFileNote(const ControllerSettingsStruct& ControllerSettings, const String& description) { + #ifdef USE_MQTT_TLS + const String certFile = ControllerSettings.getCertificateFilename(); + if (!certFile.isEmpty()) + { + const String certFile = ControllerSettings.getCertificateFilename(); + String note = description; + note += F(" "); + note += certFile; + note += F(" "); + if (fileExists(certFile)) { + note += F("(File exists)"); + } else { + note += F("(Not found)"); + } + addFormNote(note); + } + #endif +} + void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettings, controllerIndex_t controllerindex, ControllerSettingsStruct::VarType varType) { protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerindex); if (!validProtocolIndex(ProtocolIndex)) { @@ -157,35 +178,33 @@ void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettin { #ifdef USE_MQTT_TLS const int choice = static_cast(ControllerSettings.TLStype()); - #define NR_MQTT_TLS_TYPES 3 + #define NR_MQTT_TLS_TYPES 4 const __FlashStringHelper * options[NR_MQTT_TLS_TYPES] = { toString(TLS_types::NoTLS), // toString(TLS_types::TLS_PSK), toString(TLS_types::TLS_CA_CERT), + toString(TLS_types::TLS_FINGERPRINT), toString(TLS_types::TLS_insecure) }; const int indices[NR_MQTT_TLS_TYPES] = { static_cast(TLS_types::NoTLS), // static_cast(TLS_types::TLS_PSK), static_cast(TLS_types::TLS_CA_CERT), + static_cast(TLS_types::TLS_FINGERPRINT), static_cast(TLS_types::TLS_insecure) }; addFormSelector(displayName, internalName, NR_MQTT_TLS_TYPES, options, indices, choice, true); #undef NR_MQTT_TLS_TYPES - const String certFile = ControllerSettings.getCertificateFilename(); - if (!certFile.isEmpty()) - { - const String certFile = ControllerSettings.getCertificateFilename(); - String note = F("Certificate or PSK must be stored on the filesystem in "); - note += certFile; - note += F(" "); - if (fileExists(certFile)) { - note += F("(File exists)"); - } else { - note += F("(Not found)"); - } - addFormNote(note); - } + addCertificateFileNote(ControllerSettings, F("Certificate or PSK must be stored on the filesystem in")); + #endif + break; + } + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT: + { + #ifdef USE_MQTT_TLS + const bool saveDisabled = fileExists(ControllerSettings.getCertificateFilename()); + addFormCheckBox(displayName, internalName, false, saveDisabled); + addCertificateFileNote(ControllerSettings, F("Store fingerprint in")); #endif break; } @@ -353,6 +372,24 @@ void saveControllerParameterForm(ControllerSettingsStruct & ControllerSet break; } + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT: + { + #ifdef USE_MQTT_TLS + if (isFormItemChecked(internalName)) { + String fingerprint; + if (GetTLSfingerprint(fingerprint)) { + if (ControllerSettings.UseDNS) { + fingerprint += '\n'; + fingerprint += ControllerSettings.getHost(); + } + SaveCertificate(ControllerSettings.getCertificateFilename(), fingerprint); + } + } + #endif + break; + } + + case ControllerSettingsStruct::CONTROLLER_USER: setControllerUser(controllerindex, ControllerSettings, webArg(internalName)); break; diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 3e154427c0..02e2c63980 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -446,23 +446,22 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addHtml(F("")); } { - uint8_t sha256_result[32] = {0}; - if (mqtt_tls->getFingerprintSHA256(sha256_result)) { - String fingerprint; - fingerprint.reserve(64); - for (size_t i = 0; i < 32; ++i) { - fingerprint += String(sha256_result[i], HEX); - } - fingerprint.toLowerCase(); + String fingerprint; + if (GetTLSfingerprint(fingerprint)) { addFormTextBox(F("Certificate Fingerprint"), F("fingerprint"), fingerprint, 64, true); // ReadOnly + MakeControllerSettings(ControllerSettings); //-V522 + if (!AllocatedControllerSettings()) { + addHtmlError(F("Out of memory, cannot load page")); + } else { + LoadControllerSettings(controllerindex, ControllerSettings); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT); + } } } - - } #endif diff --git a/src/src/WebServer/SysInfoPage.cpp b/src/src/WebServer/SysInfoPage.cpp index cb12214040..ebb31b6272 100644 --- a/src/src/WebServer/SysInfoPage.cpp +++ b/src/src/WebServer/SysInfoPage.cpp @@ -612,8 +612,8 @@ void handle_sysinfo_Storage() { uint32_t flashDevice = (flashChipId & 0xFF00) | ((flashChipId >> 16) & 0xFF); addHtml(formatToHex(flashDevice)); } - uint32_t realSize = getFlashRealSizeInBytes(); - uint32_t ideSize = ESP.getFlashChipSize(); + const uint32_t realSize = getFlashRealSizeInBytes(); + const uint32_t ideSize = ESP.getFlashChipSize(); addRowLabel(LabelType::FLASH_CHIP_REAL_SIZE); addHtmlInt(realSize / 1024); @@ -632,17 +632,14 @@ void handle_sysinfo_Storage() { FlashMode_t ideMode = ESP.getFlashChipMode(); addRowLabel(LabelType::FLASH_IDE_MODE); { - String html; - switch (ideMode) { - case FM_QIO: html += F("QIO"); break; - case FM_QOUT: html += F("QOUT"); break; - case FM_DIO: html += F("DIO"); break; - case FM_DOUT: html += F("DOUT"); break; + case FM_QIO: addHtml(F("QIO")); break; + case FM_QOUT: addHtml(F("QOUT")); break; + case FM_DIO: addHtml(F("DIO")); break; + case FM_DOUT: addHtml(F("DOUT")); break; default: - html += getUnknownString(); break; + addHtml(getUnknownString()); break; } - addHtml(html); } # endif // if defined(ESP8266) diff --git a/src/src/WebServer/WebServer.cpp b/src/src/WebServer/WebServer.cpp index 8191c26430..83da72c798 100644 --- a/src/src/WebServer/WebServer.cpp +++ b/src/src/WebServer/WebServer.cpp @@ -1041,11 +1041,21 @@ String getControllerSymbol(uint8_t index) return ret; } */ -void addSVG_param(const String& key, float value) { + +void addSVG_param(const __FlashStringHelper * key, int value) { + addHtml(' '); + addHtml(key); + addHtml('='); + addHtml('\"'); + addHtmlInt(value); + addHtml('\"'); +} + +void addSVG_param(const __FlashStringHelper * key, float value) { addSVG_param(key, String(value, 2)); } -void addSVG_param(const String& key, const String& value) { +void addSVG_param(const __FlashStringHelper * key, const String& value) { addHtml(' '); addHtml(key); addHtml('='); @@ -1078,8 +1088,8 @@ void createSvgRect(const String& classname, addSVG_param(F("stroke"), formatToHex(strokeColor, F("#"))); addSVG_param(F("stroke-width"), strokeWidth); } - addSVG_param("x", xoffset); - addSVG_param("y", yoffset); + addSVG_param(F("x"), xoffset); + addSVG_param(F("y"), yoffset); addSVG_param(F("width"), width); addSVG_param(F("height"), height); addSVG_param(F("rx"), rx); @@ -1123,10 +1133,6 @@ void createSvgTextElement(const String& text, float textXoffset, float textYoffs #define SVG_BAR_HEIGHT 16 #define SVG_BAR_WIDTH 400 -void write_SVG_image_header(int width, int height) { - write_SVG_image_header(width, height, false); -} - void write_SVG_image_header(int width, int height, bool useViewbox) { addHtml(F(" Date: Tue, 30 Nov 2021 00:40:47 +0100 Subject: [PATCH 018/100] [MQTT TLS] Add view of certificates + option to store --- .../ControllerQueue/DelayQueueElements.cpp | 4 + .../DataStructs/ControllerSettingsStruct.cpp | 12 ++- .../DataStructs/ControllerSettingsStruct.h | 3 + src/src/ESPEasyCore/Controller.cpp | 39 ++++++-- src/src/ESPEasyCore/Controller.h | 2 + src/src/Helpers/ESPEasy_Storage.cpp | 10 ++- src/src/Helpers/ESPEasy_WiFiClientSecure.cpp | 73 ++++++++++++++- src/src/Helpers/ESPEasy_WiFiClientSecure.h | 8 +- src/src/Helpers/ESPEasy_ssl_client.h | 1 + src/src/Helpers/_CPlugin_Helper_webform.cpp | 49 ++++++++-- src/src/WebServer/ControllerPage.cpp | 89 ++++++++++++++----- 11 files changed, 245 insertions(+), 45 deletions(-) diff --git a/src/src/ControllerQueue/DelayQueueElements.cpp b/src/src/ControllerQueue/DelayQueueElements.cpp index d7d99aca43..34bc4c270e 100644 --- a/src/src/ControllerQueue/DelayQueueElements.cpp +++ b/src/src/ControllerQueue/DelayQueueElements.cpp @@ -9,6 +9,9 @@ ControllerDelayHandlerStruct *MQTTDelayHandler = nullptr; bool init_mqtt_delay_queue(controllerIndex_t ControllerIndex, String& pubname, bool& retainFlag) { + // Make sure the controller is re-connecting with the current settings. + MQTTDisconnect(); + MakeControllerSettings(ControllerSettings); //-V522 if (!AllocatedControllerSettings()) { return false; @@ -30,6 +33,7 @@ bool init_mqtt_delay_queue(controllerIndex_t ControllerIndex, String& pubname, b void exit_mqtt_delay_queue() { if (MQTTDelayHandler != nullptr) { + MQTTDisconnect(); delete MQTTDelayHandler; MQTTDelayHandler = nullptr; } diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index b5252c4257..2af808ca9b 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -328,13 +328,18 @@ void ControllerSettingsStruct::TLStype(TLS_types tls_type) } String ControllerSettingsStruct::getCertificateFilename() const +{ + return getCertificateFilename(TLStype()); +} + +String ControllerSettingsStruct::getCertificateFilename(TLS_types tls_type) const { String certFile = HostName; if (certFile.isEmpty()) { certFile = F(""); } - switch (TLStype()) { + switch (tls_type) { case TLS_types::NoTLS: case TLS_types::TLS_insecure: return EMPTY_STRING; @@ -353,6 +358,11 @@ String ControllerSettingsStruct::getCertificateFilename() const certFile += F(".fp"); break; } + + // Only use the last 29 bytes of the filename + if (certFile.length() > 28) { + certFile = certFile.substring(certFile.length() - 28); + } return certFile; } \ No newline at end of file diff --git a/src/src/DataStructs/ControllerSettingsStruct.h b/src/src/DataStructs/ControllerSettingsStruct.h index e5d529f6a7..5f4169f298 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.h +++ b/src/src/DataStructs/ControllerSettingsStruct.h @@ -66,6 +66,8 @@ struct ControllerSettingsStruct CONTROLLER_PORT, CONTROLLER_MQTT_TLS_TYPE, CONTROLLER_MQTT_TLS_STORE_FINGERPRINT, + CONTROLLER_MQTT_TLS_STORE_CERT, + CONTROLLER_MQTT_TLS_STORE_CACERT, CONTROLLER_USER, CONTROLLER_PASS, CONTROLLER_MIN_SEND_INTERVAL, @@ -154,6 +156,7 @@ struct ControllerSettingsStruct void TLStype(TLS_types tls_type); String getCertificateFilename() const; + String getCertificateFilename(TLS_types tls_type) const; boolean UseDNS; diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index ee15b26e21..66cf39f148 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -237,6 +237,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) case TLS_types::TLS_CA_CERT: { mqtt_rootCA.clear(); + /* { static int previousFree = FreeMem(); const int freemem = FreeMem(); @@ -252,6 +253,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) addLog(LOG_LEVEL_INFO, analyse); previousFree = freemem; } + */ if (mqtt_rootCA.isEmpty()) { LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_rootCA); @@ -435,18 +437,23 @@ bool MQTTConnect(controllerIndex_t controller_idx) updateMQTTclient_connected(); return false; } - String log = F("MQTT : Connected to broker with client ID: "); + if (loglevelActiveFor(LOG_LEVEL_INFO)) + { + String log = F("MQTT : Connected to broker with client ID: "); - log += clientid; - addLog(LOG_LEVEL_INFO, log); + log += clientid; + addLog(LOG_LEVEL_INFO, log); + } #ifdef USE_MQTT_TLS #ifdef ESP32 + if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log = F("MQTT : Peer certificate info: "); + String log = F("MQTT : Peer certificate info: "); + log += ControllerSettings.getHost(); + log += ' '; log += mqtt_tls->getPeerCertificateInfo(); addLog(LOG_LEVEL_INFO, log); - log.clear(); } #endif #endif @@ -454,9 +461,12 @@ bool MQTTConnect(controllerIndex_t controller_idx) parseSystemVariables(subscribeTo, false); MQTTclient.subscribe(subscribeTo.c_str()); - log = F("Subscribed to: "); - log += subscribeTo; - addLog(LOG_LEVEL_INFO, log); + if (loglevelActiveFor(LOG_LEVEL_INFO)) + { + String log = F("Subscribed to: "); + log += subscribeTo; + addLog(LOG_LEVEL_INFO, log); + } updateMQTTclient_connected(); statusLED(true); @@ -799,6 +809,19 @@ bool GetTLSfingerprint(String& fp) return false; } +bool GetTLS_Certificate(String& cert, bool caRoot) +{ + #ifdef ESP32 + if (MQTTclient_connected && mqtt_tls != nullptr) { + String subject; + if (mqtt_tls->getPeerCertificate(cert, subject, caRoot) == 0) { + return true; + } + } + #endif + return false; +} + #endif #endif // USES_MQTT diff --git a/src/src/ESPEasyCore/Controller.h b/src/src/ESPEasyCore/Controller.h index e14ac48748..4529d17375 100644 --- a/src/src/ESPEasyCore/Controller.h +++ b/src/src/ESPEasyCore/Controller.h @@ -73,6 +73,8 @@ void MQTTStatus(struct EventStruct *event, const String& status); #ifdef USE_MQTT_TLS bool GetTLSfingerprint(String& fp); +bool GetTLS_Certificate(String& cert, bool caRoot); + #endif #endif //USES_MQTT diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index b2a46b1af7..c9a216b355 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -1031,7 +1031,7 @@ bool cleanupCertificate(String & certificate, bool &changed) for (int i = 0; i < 4 && last_pos != -1; ++i) { dash_pos[i] = certificate.indexOf(F("-----"), last_pos); last_pos = dash_pos[i] + 5; - addLog(LOG_LEVEL_INFO, String(F(" dash_pos: ")) + String(dash_pos[i])); +// addLog(LOG_LEVEL_INFO, String(F(" dash_pos: ")) + String(dash_pos[i])); } if (last_pos == -1) return false; @@ -1164,7 +1164,9 @@ String InitFile(SettingsType::SettingsFileEnum file_type) \*********************************************************************************************/ String SaveToFile(const char *fname, int index, const uint8_t *memAddress, int datasize) { - return doSaveToFile(fname, index, memAddress, datasize, "r+"); + return doSaveToFile( + fname, index, memAddress, datasize, + fileExists(fname) ? "r+" : "w+"); } // See for mode description: https://github.com/esp8266/Arduino/blob/master/doc/filesystem.rst @@ -1215,7 +1217,9 @@ String doSaveToFile(const char *fname, int index, const uint8_t *memAddress, int if (f) { clearAllCaches(); SPIFFS_CHECK(f, fname); - SPIFFS_CHECK(f.seek(index, fs::SeekSet), fname); + if (index > 0) { + SPIFFS_CHECK(f.seek(index, fs::SeekSet), fname); + } const uint8_t *pointerToByteToSave = memAddress; for (int x = 0; x < datasize; x++) diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp index b9ee2e5e43..5d924ca8b0 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp @@ -22,6 +22,9 @@ #include #include +// FIXME TD-er: Feels wrong this needs to be included here to use mbedtls_pem_write_buffer +#include + #undef connect #undef write #undef read @@ -348,9 +351,11 @@ void ESPEasy_WiFiClientSecure::setAlpnProtocols(const char **alpn_protos) _alpn_protos = alpn_protos; } -String ESPEasy_WiFiClientSecure::getPeerCertificateInfo() +String ESPEasy_WiFiClientSecure::getPeerCertificateInfo(const mbedtls_x509_crt* peer) { - const mbedtls_x509_crt* peer = getPeerCertificate(); + if (peer == nullptr) { + peer = getPeerCertificate(); + } String res; if (peer != nullptr) { char buf[1024] = {0}; @@ -365,4 +370,68 @@ String ESPEasy_WiFiClientSecure::getPeerCertificateInfo() } return res; } + +int ESPEasy_WiFiClientSecure::getPeerCertificate(String& pem, String& subject, bool caRoot) +{ + const mbedtls_x509_crt *chain; + + chain = getPeerCertificate(); + + int error {0}; + bool done = false; + while (chain != nullptr && error == 0 && !done) { + if (!caRoot || (chain->ca_istrue && chain->next == nullptr)) { + done = true; + error = ESPEasy_WiFiClientSecure::cert_to_pem(chain, pem, subject); + } + chain = chain->next; + } + return error; +} + +int ESPEasy_WiFiClientSecure::cert_to_pem(const mbedtls_x509_crt *crt, String& pem, String& subject) +{ + const String pem_begin_crt = F("-----BEGIN CERTIFICATE-----\n"); + const String pem_end_crt = F("-----END CERTIFICATE-----"); + pem.clear(); + subject.clear(); + + const mbedtls_asn1_named_data* common_name = &crt->subject; + while (common_name != nullptr) { + // While iterating through DN objects, check for CN object + if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &common_name->oid)) + { + + subject.reserve(common_name->val.len); + const unsigned char* p = common_name->val.p; + for (auto i = 0; i < common_name->val.len; ++i, ++p) { + subject += static_cast(*p); + } + } + + // Fetch next DN object + common_name = common_name->next; + } + + size_t written{}; + + const size_t buffer_size = + pem_begin_crt.length() + + pem_end_crt.length() + + 2* crt->raw.len; + + std::vector pem_buf; + pem_buf.resize(buffer_size); + int ret = mbedtls_pem_write_buffer( + pem_begin_crt.c_str(), pem_end_crt.c_str(), + crt->raw.p, crt->raw.len, + &pem_buf[0], buffer_size, &written); + if (ret == 0) { + pem.reserve(written); + for (auto i = 0; i < written; ++i) { + pem += static_cast(pem_buf[i]); + } + } + return ret; +} #endif \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.h b/src/src/Helpers/ESPEasy_WiFiClientSecure.h index b5241bfda5..a35fa92bfb 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.h +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.h @@ -81,10 +81,16 @@ class ESPEasy_WiFiClientSecure : public WiFiClient bool verify(const char* fingerprint, const char* domain_name); void setHandshakeTimeout(unsigned long handshake_timeout); void setAlpnProtocols(const char **alpn_protos); - String getPeerCertificateInfo(); + String getPeerCertificateInfo(const mbedtls_x509_crt* crt = nullptr); const mbedtls_x509_crt* getPeerCertificate() { return mbedtls_ssl_get_peer_cert(&sslclient->ssl_ctx); }; bool getFingerprintSHA256(uint8_t sha256_result[32]) { return get_peer_fingerprint(sslclient, sha256_result); }; + int getPeerCertificate(String& pem, String& subject, bool caRoot); + + // See: https://stackoverflow.com/a/63730321/8708166 + static int cert_to_pem(const mbedtls_x509_crt *crt, String& pem, String& subject); + + int setTimeout(uint32_t seconds){ return 0; } operator bool() diff --git a/src/src/Helpers/ESPEasy_ssl_client.h b/src/src/Helpers/ESPEasy_ssl_client.h index 5c612c3ae7..7f6eebf034 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.h +++ b/src/src/Helpers/ESPEasy_ssl_client.h @@ -46,5 +46,6 @@ int get_ssl_receive(ESPEasy_sslclient_context *ssl_client, uint8_t *data, int le bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* fp, const char* domain_name); bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name); bool get_peer_fingerprint(ESPEasy_sslclient_context *ssl_client, uint8_t sha256[32]); + #endif #endif \ No newline at end of file diff --git a/src/src/Helpers/_CPlugin_Helper_webform.cpp b/src/src/Helpers/_CPlugin_Helper_webform.cpp index 8fe2075fdf..6628446c3f 100644 --- a/src/src/Helpers/_CPlugin_Helper_webform.cpp +++ b/src/src/Helpers/_CPlugin_Helper_webform.cpp @@ -23,6 +23,9 @@ const __FlashStringHelper * toString(ControllerSettingsStruct::VarType parameter case ControllerSettingsStruct::CONTROLLER_PORT: return F("Controller Port"); case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: return F("Use TLS"); case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT: return F("Store Fingerprint"); + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CERT: return F("Store Certificate"); + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CACERT: return F("Store CA Certificate"); + case ControllerSettingsStruct::CONTROLLER_USER: return F("Controller User"); case ControllerSettingsStruct::CONTROLLER_PASS: return F("Controller Password"); @@ -119,12 +122,11 @@ void addControllerEnabledForm(controllerIndex_t controllerindex) { addFormCheckBox(displayName, internalName, Settings.ControllerEnabled[controllerindex]); } -void addCertificateFileNote(const ControllerSettingsStruct& ControllerSettings, const String& description) { +void addCertificateFileNote(const ControllerSettingsStruct& ControllerSettings, const String& description, TLS_types tls_type) { #ifdef USE_MQTT_TLS - const String certFile = ControllerSettings.getCertificateFilename(); + const String certFile = ControllerSettings.getCertificateFilename(tls_type); if (!certFile.isEmpty()) { - const String certFile = ControllerSettings.getCertificateFilename(); String note = description; note += F(" "); note += certFile; @@ -195,19 +197,38 @@ void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettin }; addFormSelector(displayName, internalName, NR_MQTT_TLS_TYPES, options, indices, choice, true); #undef NR_MQTT_TLS_TYPES - addCertificateFileNote(ControllerSettings, F("Certificate or PSK must be stored on the filesystem in")); + addCertificateFileNote(ControllerSettings, F("Certificate or PSK must be stored on the filesystem in"), ControllerSettings.TLStype()); #endif break; } case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT: { #ifdef USE_MQTT_TLS - const bool saveDisabled = fileExists(ControllerSettings.getCertificateFilename()); + const bool saveDisabled = fileExists(ControllerSettings.getCertificateFilename(TLS_types::TLS_FINGERPRINT)); addFormCheckBox(displayName, internalName, false, saveDisabled); - addCertificateFileNote(ControllerSettings, F("Store fingerprint in")); + addCertificateFileNote(ControllerSettings, F("Store fingerprint in"), TLS_types::TLS_FINGERPRINT); #endif break; } + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CERT: + // fall through + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CACERT: + { + #ifdef USE_MQTT_TLS + /* + const TLS_types tls_type = (varType == ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CACERT) ? + TLS_types::TLS_CA_CERT : TLS_types::TLS_CERT; + */ + const bool saveDisabled = fileExists(ControllerSettings.getCertificateFilename(TLS_types::TLS_CA_CERT)); + addFormCheckBox(displayName, internalName, false, saveDisabled); + if (saveDisabled) { + addUnit(F("File Exists")); + } + addCertificateFileNote(ControllerSettings, F("Store CA Certificate in"), TLS_types::TLS_CA_CERT); + #endif + break; + } + case ControllerSettingsStruct::CONTROLLER_USER: { const size_t fieldMaxLength = @@ -382,13 +403,27 @@ void saveControllerParameterForm(ControllerSettingsStruct & ControllerSet fingerprint += '\n'; fingerprint += ControllerSettings.getHost(); } - SaveCertificate(ControllerSettings.getCertificateFilename(), fingerprint); + SaveCertificate(ControllerSettings.getCertificateFilename(TLS_types::TLS_FINGERPRINT), fingerprint); } } #endif break; } + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CERT: + // fall through + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CACERT: + { + #ifdef USE_MQTT_TLS + if (isFormItemChecked(internalName)) { + String cacert; + if (GetTLS_Certificate(cacert, true)) { + SaveCertificate(ControllerSettings.getCertificateFilename(TLS_types::TLS_CA_CERT), cacert); + } + } + #endif + break; + } case ControllerSettingsStruct::CONTROLLER_USER: setControllerUser(controllerindex, ControllerSettings, webArg(internalName)); diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 02e2c63980..9824f31af1 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -27,7 +27,6 @@ #include "../Helpers/StringConverter.h" - // ******************************************************************************** // Web Interface controller page // ******************************************************************************** @@ -437,34 +436,78 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex #ifdef ESP32 if (MQTTclient_connected && mqtt_tls != nullptr) { - addFormSubHeader(F("Peer Certificate")); - - { - addRowLabel(F("Certificate Info")); - addHtml(F("")); - } - { - String fingerprint; - if (GetTLSfingerprint(fingerprint)) { - addFormTextBox(F("Certificate Fingerprint"), - F("fingerprint"), - fingerprint, - 64, - true); // ReadOnly - MakeControllerSettings(ControllerSettings); //-V522 - if (!AllocatedControllerSettings()) { - addHtmlError(F("Out of memory, cannot load page")); - } else { - LoadControllerSettings(controllerindex, ControllerSettings); + MakeControllerSettings(ControllerSettings); //-V522 + if (!AllocatedControllerSettings()) { + addHtmlError(F("Out of memory, cannot load page")); + } else { + LoadControllerSettings(controllerindex, ControllerSettings); + + addFormSubHeader(F("Peer Certificate")); + + { + addRowLabel(F("Certificate Info")); + addHtml(F("")); + } + { + String fingerprint; + if (GetTLSfingerprint(fingerprint)) { + addFormTextBox(F("Certificate Fingerprint"), + F("fingerprint"), + fingerprint, + 64, + true); // ReadOnly addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT); } } + addFormSubHeader(F("Peer Certificate Chain")); + { + // FIXME TD-er: Must wrap this in divs to be able to fold it by default. + const mbedtls_x509_crt *chain; + + chain = mqtt_tls->getPeerCertificate(); + + int error {0}; + while (chain != nullptr && error == 0) { + /* + const bool mustShow = !chain->ca_istrue || chain->next == nullptr; + if (mustShow) { + */ + String pem, subject; + error = ESPEasy_WiFiClientSecure::cert_to_pem(chain, pem, subject); + { + String label; + if (chain->ca_istrue) { + label = F("CA "); + } + label += F("Certificate "); + label += subject; + label += F(""); + addRowLabel(label); + } + if (error == 0) { + addHtml(F("")); + + addHtml(F("")); + } else { + addHtmlInt(error); + } + if (chain->ca_istrue && chain->next == nullptr) { + // Add checkbox to store CA cert + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CACERT); + } +// } + chain = chain->next; + } + } } } #endif - } #endif } From f3b617c897d7056e59e73f0c46cf7ab02be64432 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 25 Sep 2021 21:22:10 +0200 Subject: [PATCH 019/100] [MQTT TLS] Add TLS support for MQTT --- docs/source/Controller/C016.rst | 2 +- docs/source/Controller/_Controller.rst | 63 +++++++++++++ platformio_esp82xx_envs.ini | 1 + .../DataStructs/ControllerSettingsStruct.cpp | 40 ++++++++ .../DataStructs/ControllerSettingsStruct.h | 6 ++ src/src/DataStructs/ProtocolStruct.cpp | 11 ++- src/src/DataStructs/ProtocolStruct.h | 37 ++++---- src/src/DataTypes/TLS_types.cpp | 12 +++ src/src/DataTypes/TLS_types.h | 20 ++++ src/src/ESPEasyCore/Controller.cpp | 93 ++++++++++++++++++- src/src/Globals/MQTT.cpp | 17 +++- src/src/Globals/MQTT.h | 22 ++++- src/src/Helpers/_CPlugin_Helper_webform.cpp | 35 ++++++- src/src/WebServer/ControllerPage.cpp | 8 +- tools/pio/pre_custom_esp32.py | 1 + tools/pio/pre_custom_esp82xx.py | 1 + 16 files changed, 340 insertions(+), 29 deletions(-) create mode 100644 src/src/DataTypes/TLS_types.cpp create mode 100644 src/src/DataTypes/TLS_types.h diff --git a/docs/source/Controller/C016.rst b/docs/source/Controller/C016.rst index 53ee64861d..40eb6484cb 100644 --- a/docs/source/Controller/C016.rst +++ b/docs/source/Controller/C016.rst @@ -45,7 +45,7 @@ Each time a plugin sends data to this controller, a sample set is stored. A typical sample set contains: -- Timestamp (Default: Unix Time, but can be switched to "local time" in the controller settings) +- Timestamp (Default: Unix Time, but can be switched to "local time" in the controller settings with the "Use Local System Time" checkbox) - task index delivering the data - 4 float values diff --git a/docs/source/Controller/_Controller.rst b/docs/source/Controller/_Controller.rst index e990276c9d..ed2ee60359 100644 --- a/docs/source/Controller/_Controller.rst +++ b/docs/source/Controller/_Controller.rst @@ -74,6 +74,69 @@ before WiFi connection is made or during lost connection. For almost all controllers, sending data is a blocking call, so it may halt execution of other code on the node. With timouts longer than 2 seconds, the ESP may reboot as the software watchdog may step in. +TLS configuration +----------------- + +Added: 2021-09-26 + +Some protocols like MQTT may use TLS to provide a secure connection to the broker. + + +Still under development. +Notes: + +BearSSL::WiFiClientSecure net; + +Retrieve CA root certificate: +net.setCACert(local_root_ca); +BearSSL::X509List cert(digicert); +net.setTrustAnchors(&cert); + + +Retrieve public key of a specific certificate: ``openssl x509 -pubkey -noout -in ca.crt`` +BearSSL::PublicKey key(pubkey); +net.setKnownKey(&key); + + +Use certificate fingerprint (HEX checksum of certificate): +openssl x509 -fingerrint -in ca.crt + +net.setFingerprint(fp); + +Self Signed certificate Mosquitto: http://www.steves-internet-guide.com/mosquitto-tls/ +Let's encrypt Mosquitto: https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-the-mosquitto-mqtt-messaging-broker-on-ubuntu-18-04-quickstart + +See: https://www.youtube.com/watch?v=ytQUbyab4es + +https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt + +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- Sample ThingSpeak configuration diff --git a/platformio_esp82xx_envs.ini b/platformio_esp82xx_envs.ini index 33f3b11b1f..6b40e8e71b 100644 --- a/platformio_esp82xx_envs.ini +++ b/platformio_esp82xx_envs.ini @@ -36,6 +36,7 @@ build_flags = ${regular_platform.build_flags} -DPLUGIN_BUILD_CUSTOM lib_ignore = ${esp8266_custom_common.lib_ignore} extra_scripts = ${esp8266_custom_common.extra_scripts} +board_build.f_cpu = 160000000L [env:custom_IR_ESP8266_4M1M] extends = esp8266_4M1M diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index f06a37379f..871daa4f28 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -3,6 +3,7 @@ #include "../../ESPEasy_common.h" #include "../CustomBuild/ESPEasyLimits.h" +#include "../ESPEasyCore/ESPEasy_Log.h" #include "../ESPEasyCore/ESPEasyNetwork.h" #include "../Helpers/Misc.h" #include "../Helpers/Networking.h" @@ -14,6 +15,7 @@ #include #include + ControllerSettingsStruct::ControllerSettingsStruct() { reset(); @@ -75,6 +77,29 @@ void ControllerSettingsStruct::validate() { ZERO_TERMINATE(MQTTLwtTopic); ZERO_TERMINATE(LWTMessageConnect); ZERO_TERMINATE(LWTMessageDisconnect); + + #ifdef USES_MQTT + #ifdef USE_MQTT_TLS + if (TLStype() == TLS_types::NoTLS) { + if (Port == 8883) { + Port = 1883; + addLog(LOG_LEVEL_ERROR, F("Not using TLS, but port set to secure 8883. Use port 1883 instead")); + } + } else { + if (Port == 1883) { + Port = 8883; + addLog(LOG_LEVEL_ERROR, F("Using TLS, but port set to insecure port 1883. Use port 8883 instead")); + } + } + #else + if (Port == 8883) { + // No TLS support, so when switching builds, make sure it can still work. + Port = 1883; + addLog(LOG_LEVEL_ERROR, F("Not using TLS, but port set to secure 8883. Use port 1883 instead")); + } + #endif + #endif + } IPAddress ControllerSettingsStruct::getIP() const { @@ -286,3 +311,18 @@ void ControllerSettingsStruct::useLocalSystemTime(bool value) { bitWrite(VariousFlags, 11, value); } + +TLS_types ControllerSettingsStruct::TLStype() const +{ + // Store it in bits 12, 13, 14 + const TLS_types tls_type = static_cast((VariousFlags >> 12) & 0x7); + return tls_type; +} + +void ControllerSettingsStruct::TLStype(TLS_types tls_type) +{ + const uint32_t mask = ~(0x7); + VariousFlags &= mask; // Clear the bits + const uint32_t tls_type_val = static_cast(tls_type) << 12; + VariousFlags |= tls_type_val; +} diff --git a/src/src/DataStructs/ControllerSettingsStruct.h b/src/src/DataStructs/ControllerSettingsStruct.h index e37fec1889..3fcc8d1b49 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.h +++ b/src/src/DataStructs/ControllerSettingsStruct.h @@ -9,6 +9,7 @@ #include // for std::nothrow #include "../../ESPEasy_common.h" +#include "../DataTypes/TLS_types.h" #include "../Globals/Plugins.h" class IPAddress; @@ -63,6 +64,7 @@ struct ControllerSettingsStruct CONTROLLER_HOSTNAME, CONTROLLER_IP, CONTROLLER_PORT, + CONTROLLER_MQTT_TLS_TYPE, CONTROLLER_USER, CONTROLLER_PASS, CONTROLLER_MIN_SEND_INTERVAL, @@ -145,6 +147,10 @@ struct ControllerSettingsStruct bool useLocalSystemTime() const; void useLocalSystemTime(bool value); + + + TLS_types TLStype() const; + void TLStype(TLS_types tls_type); boolean UseDNS; diff --git a/src/src/DataStructs/ProtocolStruct.cpp b/src/src/DataStructs/ProtocolStruct.cpp index d0ed32f245..7cd351583e 100644 --- a/src/src/DataStructs/ProtocolStruct.cpp +++ b/src/src/DataStructs/ProtocolStruct.cpp @@ -1,10 +1,11 @@ #include "../DataStructs/ProtocolStruct.h" ProtocolStruct::ProtocolStruct() : - defaultPort(0), Number(0), usesMQTT(false), usesAccount(false), usesPassword(false), - usesTemplate(false), usesID(false), Custom(false), usesHost(true), usesPort(true), - usesQueue(true), usesCheckReply(true), usesTimeout(true), usesSampleSets(false), - usesExtCreds(false), needsNetwork(true), allowsExpire(true), allowLocalSystemTime(false) {} + defaultPort(0), Number(0), usesMQTT(false), usesAccount(false), usesPassword(false), + usesTemplate(false), usesID(false), Custom(false), usesHost(true), usesPort(true), + usesQueue(true), usesCheckReply(true), usesTimeout(true), usesSampleSets(false), + usesExtCreds(false), needsNetwork(true), allowsExpire(true), allowLocalSystemTime(false), + usesTLS(false) {} bool ProtocolStruct::useCredentials() const { return usesAccount || usesPassword; @@ -15,4 +16,4 @@ bool ProtocolStruct::useExtendedCredentials() const { return useCredentials(); } return false; -} \ No newline at end of file +} diff --git a/src/src/DataStructs/ProtocolStruct.h b/src/src/DataStructs/ProtocolStruct.h index e580f0b6b5..e8f7737471 100644 --- a/src/src/DataStructs/ProtocolStruct.h +++ b/src/src/DataStructs/ProtocolStruct.h @@ -16,23 +16,26 @@ struct ProtocolStruct bool useExtendedCredentials() const; uint16_t defaultPort; - uint8_t Number; - bool usesMQTT : 1; - bool usesAccount : 1; - bool usesPassword : 1; - bool usesTemplate : 1; // When set, the protocol will pre-load some templates like default MQTT topics - bool usesID : 1; // Whether a controller supports sending an IDX value sent along with plugin data - bool Custom : 1; // When set, the controller has to define all parameters on the controller setup page - bool usesHost : 1; - bool usesPort : 1; - bool usesQueue : 1; - bool usesCheckReply : 1; - bool usesTimeout : 1; - bool usesSampleSets : 1; - bool usesExtCreds : 1; - bool needsNetwork : 1; - bool allowsExpire : 1; - bool allowLocalSystemTime : 1; + uint8_t Number; + bool usesMQTT : 1; // Indicating whether it is a MQTT controller + bool usesAccount : 1; // Offer to enter credentials + bool usesPassword : 1; + bool usesTemplate : 1; // When set, the protocol will pre-load some templates like default MQTT topics + bool usesID : 1; // Whether a controller supports sending an IDX value sent along with plugin data + bool Custom : 1; // When set, the controller has to define all parameters on the controller setup page + bool usesHost : 1; // Offer either DNS hostname or IP + bool usesPort : 1; // Offer to set a port nr. This can be network port, but depending on controller this may be a + // different type of port. See LoRaWAN for example. + bool usesQueue : 1; // Allow to queue messages + bool usesCheckReply : 1; // Allow optional wait for reply + bool usesTimeout : 1; // Offer to set a timeout. + bool usesSampleSets : 1; // A sample set is an extra counter which is incremented as soon as a new value of set task is seen. + // (to keep track of bursts of messages where some may be lost) + bool usesExtCreds : 1; // Offer to store longer credentials + bool needsNetwork : 1; // Whether it needs a network connection to work + bool allowsExpire : 1; // Whether queued messages may be removed from the queue after some time + bool allowLocalSystemTime : 1; // Allow switching between Unix time and local time (including timezone and DST) + bool usesTLS : 1; // May offer TLS related settings and options }; typedef std::vector ProtocolVector; diff --git a/src/src/DataTypes/TLS_types.cpp b/src/src/DataTypes/TLS_types.cpp new file mode 100644 index 0000000000..5eaf38b4b1 --- /dev/null +++ b/src/src/DataTypes/TLS_types.cpp @@ -0,0 +1,12 @@ +#include "../DataTypes/TLS_types.h" + +const __FlashStringHelper* toString(TLS_types tls_type) +{ + switch (tls_type) { + case TLS_types::NoTLS: break; + case TLS_types::TLS_PSK: return F("TLS PreSharedKey"); + case TLS_types::TLS_CA_CERT: return F("TLS CA Cert"); + case TLS_types::TLS_insecure: return F("TLS No Checks (insecure)"); + } + return F("No TLS"); +} diff --git a/src/src/DataTypes/TLS_types.h b/src/src/DataTypes/TLS_types.h new file mode 100644 index 0000000000..8cf0e257d5 --- /dev/null +++ b/src/src/DataTypes/TLS_types.h @@ -0,0 +1,20 @@ +#ifndef DATATYPES_TLS_TYPES_H +#define DATATYPES_TLS_TYPES_H + + +#include +#include + +// Value is stored, so do not change assigned integer values. +enum class TLS_types { + NoTLS = 0, // Do not use encryption + TLS_PSK = 1, // Pre-Shared-Key + TLS_CA_CERT = 2, // Validate server certificate against known CA +//TLS_CA_CLI_CERT = 3, // TLS_CA_CERT + supply client certificate for authentication + TLS_insecure = 7 // Set as last option, do not check supplied certificate. Ideal for man-in-the-middle attack. +}; + +const __FlashStringHelper* toString(TLS_types tls_type); + + +#endif // ifndef DATATYPES_TLS_TYPES_H diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 6791e2898b..59b51f94ec 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -190,13 +190,73 @@ bool MQTTConnect(controllerIndex_t controller_idx) // mqtt = WiFiClient(); // workaround see: https://github.com/esp8266/Arduino/issues/4497#issuecomment-373023864 delay(0); + + uint16_t mqttPort = ControllerSettings.Port; + +#ifdef USE_MQTT_TLS + mqtt_tls_last_errorstr = EMPTY_STRING; + mqtt_tls_last_error = 0; + const TLS_types TLS_type = ControllerSettings.TLStype(); + switch(TLS_type) { + case TLS_types::NoTLS: + { + mqtt.setTimeout(ControllerSettings.ClientTimeout); + MQTTclient.setClient(mqtt); + break; + } + case TLS_types::TLS_PSK: + { + //mqtt_tls.setPreSharedKey(const char *pskIdent, const char *psKey); // psKey in Hex + break; + } + case TLS_types::TLS_CA_CERT: + { + #ifdef ESP32 + mqtt_tls.setCACert(mqtt_rootCA); + #endif + #ifdef ESP8266 + mqtt_X509List.append(mqtt_rootCA); + mqtt_tls.setTrustAnchors(&mqtt_X509List); + #endif + break; + } + /* + case TLS_types::TLS_CA_CLI_CERT: + { + //mqtt_tls.setCertificate(const char *client_ca); + break; + } + */ + case TLS_types::TLS_insecure: + { + mqtt_tls.setInsecure(); + break; + } + } + if (TLS_type != TLS_types::NoTLS) { + mqtt_tls.setTimeout(ControllerSettings.ClientTimeout); + #ifdef ESP8266 + mqtt_tls.setBufferSizes(1024,1024); + #endif + MQTTclient.setClient(mqtt_tls); + if (mqttPort == 1883) { + mqttPort = 8883; + } + } else { + if (mqttPort == 8883) { + mqttPort = 1883; + } + } + +#else mqtt.setTimeout(ControllerSettings.ClientTimeout); MQTTclient.setClient(mqtt); +#endif if (ControllerSettings.UseDNS) { - MQTTclient.setServer(ControllerSettings.getHost().c_str(), ControllerSettings.Port); + MQTTclient.setServer(ControllerSettings.getHost().c_str(), mqttPort); } else { - MQTTclient.setServer(ControllerSettings.getIP(), ControllerSettings.Port); + MQTTclient.setServer(ControllerSettings.getIP(), mqttPort); } MQTTclient.setCallback(incoming_mqtt_callback); @@ -210,6 +270,8 @@ bool MQTTConnect(controllerIndex_t controller_idx) bool willRetain = ControllerSettings.mqtt_willRetain() && ControllerSettings.mqtt_sendLWT(); bool cleanSession = ControllerSettings.mqtt_cleanSession(); // As suggested here: + mqtt_last_connect_attempt.setNow(); + // https://github.com/knolleary/pubsubclient/issues/458#issuecomment-493875150 if (hasControllerCredentialsSet(controller_idx, ControllerSettings)) { @@ -238,8 +300,31 @@ bool MQTTConnect(controllerIndex_t controller_idx) uint8_t controller_number = Settings.Protocol[controller_idx]; count_connection_results(MQTTresult, F("MQTT : Broker "), controller_number); + #ifdef USE_MQTT_TLS + { + char buf[128] = {0}; + #ifdef ESP8266 + mqtt_tls_last_error = mqtt_tls.getLastSSLError(buf,128); + #endif + #ifdef ESP32 + mqtt_tls_last_error = mqtt_tls.lastError(buf,128); + #endif + mqtt_tls_last_errorstr = buf; + } + #endif + if (!MQTTresult) { + #ifdef USE_MQTT_TLS + if ((mqtt_tls_last_error != 0) && loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = F("MQTT : TLS error code: "); + log += mqtt_tls_last_error; + log += ' '; + log += mqtt_tls_last_errorstr; + addLog(LOG_LEVEL_ERROR, log); + } + #endif + MQTTclient.disconnect(); updateMQTTclient_connected(); return false; @@ -352,6 +437,10 @@ bool MQTTCheck(controllerIndex_t controller_idx) if (MQTTclient_should_reconnect || !MQTTclient.connected()) { + if (mqtt_last_connect_attempt.isSet() && mqtt_last_connect_attempt.millisPassedSince() < 5000) { + return false; + } + if (MQTTclient_should_reconnect) { addLog(LOG_LEVEL_ERROR, F("MQTT : Intentional reconnect")); } diff --git a/src/src/Globals/MQTT.cpp b/src/src/Globals/MQTT.cpp index 80f80bd690..10efc391c9 100644 --- a/src/src/Globals/MQTT.cpp +++ b/src/src/Globals/MQTT.cpp @@ -6,12 +6,27 @@ // MQTT client -WiFiClient mqtt; +WiFiClient mqtt; +# ifdef USE_MQTT_TLS +String mqtt_tls_last_errorstr; +int32_t mqtt_tls_last_error = 0; + +# ifdef ESP32 +WiFiClientSecure mqtt_tls; +# endif // ifdef ESP32 +# ifdef ESP8266 +BearSSL::WiFiClientSecure mqtt_tls; +BearSSL::X509List mqtt_X509List; +# endif // ifdef ESP8266 +const char *mqtt_rootCA = nullptr; +# endif // ifdef USE_MQTT_TLS + PubSubClient MQTTclient(mqtt); bool MQTTclient_should_reconnect = true; bool MQTTclient_must_send_LWT_connected = false; bool MQTTclient_connected = false; int mqtt_reconnect_count = 0; +LongTermTimer mqtt_last_connect_attempt; #endif // USES_MQTT #ifdef USES_P037 diff --git a/src/src/Globals/MQTT.h b/src/src/Globals/MQTT.h index 69ef6b3104..af673b4e6f 100644 --- a/src/src/Globals/MQTT.h +++ b/src/src/Globals/MQTT.h @@ -5,17 +5,37 @@ #ifdef USES_MQTT +# include "../Helpers/LongTermTimer.h" # include # include +# ifdef USE_MQTT_TLS +# include +# endif // ifdef USE_MQTT_TLS + // MQTT client -extern WiFiClient mqtt; +extern WiFiClient mqtt; +# ifdef USE_MQTT_TLS +extern String mqtt_tls_last_errorstr; +extern int32_t mqtt_tls_last_error; +# ifdef ESP32 +extern WiFiClientSecure mqtt_tls; +# endif // ifdef ESP32 +# ifdef ESP8266 +extern BearSSL::WiFiClientSecure mqtt_tls; +extern BearSSL::X509List mqtt_X509List; + +# endif // ifdef ESP8266 + +extern const char *mqtt_rootCA; +# endif // ifdef USE_MQTT_TLS extern PubSubClient MQTTclient; extern bool MQTTclient_should_reconnect; extern bool MQTTclient_must_send_LWT_connected; extern bool MQTTclient_connected; extern int mqtt_reconnect_count; +extern LongTermTimer mqtt_last_connect_attempt; #endif // USES_MQTT #ifdef USES_P037 diff --git a/src/src/Helpers/_CPlugin_Helper_webform.cpp b/src/src/Helpers/_CPlugin_Helper_webform.cpp index 7365dc6a27..09b0c0d5e4 100644 --- a/src/src/Helpers/_CPlugin_Helper_webform.cpp +++ b/src/src/Helpers/_CPlugin_Helper_webform.cpp @@ -20,7 +20,8 @@ const __FlashStringHelper * toString(ControllerSettingsStruct::VarType parameter case ControllerSettingsStruct::CONTROLLER_USE_DNS: return F("Locate Controller"); case ControllerSettingsStruct::CONTROLLER_HOSTNAME: return F("Controller Hostname"); case ControllerSettingsStruct::CONTROLLER_IP: return F("Controller IP"); - case ControllerSettingsStruct::CONTROLLER_PORT: return F("Controller Port"); + case ControllerSettingsStruct::CONTROLLER_PORT: return F("Controller Port"); + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: return F("Use TLS"); case ControllerSettingsStruct::CONTROLLER_USER: return F("Controller User"); case ControllerSettingsStruct::CONTROLLER_PASS: return F("Controller Password"); @@ -153,6 +154,28 @@ void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettin addFormNumericBox(displayName, internalName, ControllerSettings.Port, 1, 65535); break; } + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: + { + #ifdef USE_MQTT_TLS + const int choice = static_cast(ControllerSettings.TLStype()); + #define NR_MQTT_TLS_TYPES 3 + const __FlashStringHelper * options[NR_MQTT_TLS_TYPES] = { + toString(TLS_types::NoTLS), +// toString(TLS_types::TLS_PSK), +// toString(TLS_types::TLS_CA_CERT), + toString(TLS_types::TLS_insecure) + }; + const int indices[NR_MQTT_TLS_TYPES] = { + static_cast(TLS_types::NoTLS), +// static_cast(TLS_types::TLS_PSK), +// static_cast(TLS_types::TLS_CA_CERT), + static_cast(TLS_types::TLS_insecure) + }; + addFormSelector(displayName, internalName, NR_MQTT_TLS_TYPES, options, indices, choice, true); + #undef NR_MQTT_TLS_TYPES + #endif + break; + } case ControllerSettingsStruct::CONTROLLER_USER: { const size_t fieldMaxLength = @@ -309,6 +332,16 @@ void saveControllerParameterForm(ControllerSettingsStruct & ControllerSet case ControllerSettingsStruct::CONTROLLER_PORT: ControllerSettings.Port = getFormItemInt(internalName, ControllerSettings.Port); break; + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: + { + #ifdef USE_MQTT_TLS + const int current = static_cast(ControllerSettings.TLStype()); + const TLS_types tls_type = static_cast(getFormItemInt(internalName, current)); + ControllerSettings.TLStype(tls_type); + #endif + break; + } + case ControllerSettingsStruct::CONTROLLER_USER: setControllerUser(controllerindex, ControllerSettings, webArg(internalName)); break; diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 502182c938..788dc385f5 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -135,6 +135,7 @@ void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSet ControllerSettings.reset(); ControllerSettings.Port = Protocol[ProtocolIndex].defaultPort; + ControllerSettings.TLStype(TLS_types::NoTLS); // Load some templates from the controller. struct EventStruct TempEvent; @@ -298,7 +299,6 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addHtmlError(F("Out of memory, cannot load page")); } else { LoadControllerSettings(controllerindex, ControllerSettings); - if (!Protocol[ProtocolIndex].Custom) { if (Protocol[ProtocolIndex].usesHost) { @@ -316,6 +316,12 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex if (Protocol[ProtocolIndex].usesPort) { addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PORT); } + #ifdef USES_MQTT + if (Protocol[ProtocolIndex].usesMQTT && Protocol[ProtocolIndex].usesTLS) { + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE); + addFormNote(F("Default ports: MQTT: 1883 / MQTT TLS: 8883")); + } + #endif if (Protocol[ProtocolIndex].usesQueue) { addTableSeparator(F("Controller Queue"), 2, 3); diff --git a/tools/pio/pre_custom_esp32.py b/tools/pio/pre_custom_esp32.py index a97afcb4a4..d75e5b2bc5 100644 --- a/tools/pio/pre_custom_esp32.py +++ b/tools/pio/pre_custom_esp32.py @@ -61,6 +61,7 @@ "-DFEATURE_SD", "-DFEATURE_I2CMULTIPLEXER", "-DUSE_TRIGONOMETRIC_FUNCTIONS_RULES", + "-DUSE_MQTT_TLS", "-DUSE_SETTINGS_ARCHIVE" ] diff --git a/tools/pio/pre_custom_esp82xx.py b/tools/pio/pre_custom_esp82xx.py index bc3ee3d5ce..00a5d15366 100644 --- a/tools/pio/pre_custom_esp82xx.py +++ b/tools/pio/pre_custom_esp82xx.py @@ -59,6 +59,7 @@ # "-DFEATURE_MDNS", # "-DFEATURE_SD", "-DUSE_EXT_RTC", + "-DUSE_MQTT_TLS", "-DFEATURE_I2CMULTIPLEXER", "-DUSE_TRIGONOMETRIC_FUNCTIONS_RULES", From 30465332a324df290b3c985cbe18c851b684dfc6 Mon Sep 17 00:00:00 2001 From: TD-er Date: Tue, 19 Oct 2021 16:02:40 +0200 Subject: [PATCH 020/100] [MQTT TLS] Add cert.py to extract certificate info --- platformio_core_defs.ini | 2 +- tools/pio/cert.py | 127 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 tools/pio/cert.py diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index 1a51b13f78..8a9a7c63e1 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -165,7 +165,7 @@ build_flags = -DESP32_STAGE [core_esp32_3_3_0] platform = espressif32 @ 3.3.0 platform_packages = framework-arduinoespressif32 -build_flags = -DESP32_STAGE +build_flags = [core_esp32_3_3_2] platform = espressif32 @ 3.3.2 diff --git a/tools/pio/cert.py b/tools/pio/cert.py new file mode 100644 index 0000000000..3ade0f5ccc --- /dev/null +++ b/tools/pio/cert.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 + +# Script to download/update certificates and public keys +# and generate compilable source files for c++/Arduino. +# released to public domain + +# Original: https://github.com/esp8266/Arduino/blob/master/tools/cert.py + +import urllib.request +import re +import ssl +import sys +import socket +import argparse +import datetime + +from cryptography import x509 +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.serialization import pkcs7 +from cryptography.hazmat.primitives.serialization import Encoding +from cryptography.hazmat.primitives.serialization import PublicFormat + +def printData(data, showPub = True): + try: + xcert = x509.load_der_x509_certificate(data) + except: + try: + xcert = x509.load_pem_x509_certificate(data) + except: + try: + xcert = pkcs7.load_der_pkcs7_certificates(data) + except: + xcert = pkcs7.load_pem_pkcs7_certificates(data) + if len(xcert) > 1: + print('// Warning: TODO: pkcs7 has {} entries'.format(len(xcert))) + xcert = xcert[0] + + cn = '' + for dn in xcert.subject.rfc4514_string().split(','): + keyval = dn.split('=') + if keyval[0] == 'CN': + cn += keyval[1] + name = re.sub('[^a-zA-Z0-9_]', '_', cn) + print('// CN: {} => name: {}'.format(cn, name)) + + print('// not valid before:', xcert.not_valid_before) + print('// not valid after: ', xcert.not_valid_after) + + if showPub: + + fingerprint = xcert.fingerprint(hashes.SHA1()).hex(':') + print('const char fingerprint_{} [] PROGMEM = "{}";'.format(name, fingerprint)) + + pem = xcert.public_key().public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo).decode('utf-8') + print('const char pubkey_{} [] PROGMEM = R"PUBKEY('.format(name)) + print(pem + ')PUBKEY";') + + else: + + cert = xcert.public_bytes(Encoding.PEM).decode('utf-8') + print('const char cert_{} [] PROGMEM = R"CERT('.format(name)) + print(cert + ')CERT";') + + cas = [] + for ext in xcert.extensions: + if ext.oid == x509.ObjectIdentifier("1.3.6.1.5.5.7.1.1"): + for desc in ext.value: + if desc.access_method == x509.oid.AuthorityInformationAccessOID.CA_ISSUERS: + cas.append(desc.access_location.value) + for ca in cas: + with urllib.request.urlopen(ca) as crt: + print() + print('// ' + ca) + printData(crt.read(), False) + print() + +def get_certificate(hostname, port, name): + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + with socket.create_connection((hostname, port)) as sock: + with context.wrap_socket(sock, server_hostname=hostname) as ssock: + print('////////////////////////////////////////////////////////////') + print('// certificate chain for {}:{}'.format(hostname, port)) + print() + if name: + print('const char* {}_host = "{}";'.format(name, hostname)); + print('const uint16_t {}_port = {};'.format(name, port)); + print() + printData(ssock.getpeercert(binary_form=True)) + print('// end of certificate chain for {}:{}'.format(hostname, port)) + print('////////////////////////////////////////////////////////////') + print() + return 0 + +def main(): + parser = argparse.ArgumentParser(description='download certificate chain and public keys under a C++/Arduino compilable form') + parser.add_argument('-s', '--server', action='store', required=True, help='TLS server dns name') + parser.add_argument('-p', '--port', action='store', required=False, help='TLS server port') + parser.add_argument('-n', '--name', action='store', required=False, help='variable name') + port = 443 + args = parser.parse_args() + server = args.server + port = 443 + try: + split = server.split(':') + server = split[0] + port = int(split[1]) + except: + pass + try: + port = int(args.port) + except: + pass + + print() + print('// this file is autogenerated - any modification will be overwritten') + print('// unused symbols will not be linked in the final binary') + print('// generated on {}'.format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) + print('// by {}'.format(sys.argv)) + print() + print('#pragma once') + print() + return get_certificate(server, port, args.name) + +if __name__ == '__main__': + sys.exit(main()) From 4ffa604c47f542e57cde45aed902f8308d299713 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 01:32:14 +0100 Subject: [PATCH 021/100] [MQTT TLS] Allow to load CA root cert from file --- src/src/DataStructs/Caches.h | 8 +-- .../DataStructs/ControllerSettingsStruct.cpp | 33 +++++++++- .../DataStructs/ControllerSettingsStruct.h | 2 + src/src/DataTypes/TLS_types.h | 10 +-- src/src/ESPEasyCore/Controller.cpp | 29 ++++++--- src/src/Globals/MQTT.cpp | 2 +- src/src/Globals/MQTT.h | 2 +- src/src/Helpers/ESPEasy_Storage.cpp | 62 +++++++++++++++++-- src/src/Helpers/ESPEasy_Storage.h | 4 ++ src/src/Helpers/_CPlugin_Helper_webform.cpp | 18 +++++- src/src/WebServer/WebServer.cpp | 1 - 11 files changed, 143 insertions(+), 28 deletions(-) diff --git a/src/src/DataStructs/Caches.h b/src/src/DataStructs/Caches.h index 4960c0436d..f1ebb331b1 100644 --- a/src/src/DataStructs/Caches.h +++ b/src/src/DataStructs/Caches.h @@ -5,9 +5,9 @@ #include "../../ESPEasy_common.h" #include "../Globals/Plugins.h" -typedef std::mapTaskIndexNameMap; -typedef std::map TaskIndexValueNameMap; -typedef std::map FilePresenceMap; +typedef std::map TaskIndexNameMap; +typedef std::map TaskIndexValueNameMap; +typedef std::map FilePresenceMap; struct Caches { void clearAllCaches(); @@ -18,7 +18,7 @@ struct Caches { TaskIndexNameMap taskIndexName; TaskIndexValueNameMap taskIndexValueName; - FilePresenceMap fileExistsMap; + FilePresenceMap fileExistsMap; // Filesize. -1 if not present bool activeTaskUseSerial0 = false; }; diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index 871daa4f28..791013cece 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -314,15 +314,42 @@ void ControllerSettingsStruct::useLocalSystemTime(bool value) TLS_types ControllerSettingsStruct::TLStype() const { - // Store it in bits 12, 13, 14 - const TLS_types tls_type = static_cast((VariousFlags >> 12) & 0x7); + // Store it in bits 12, 13, 14, 15 + const TLS_types tls_type = static_cast((VariousFlags >> 12) & 0xF); return tls_type; } void ControllerSettingsStruct::TLStype(TLS_types tls_type) { - const uint32_t mask = ~(0x7); + const uint32_t mask = ~(0xF); VariousFlags &= mask; // Clear the bits const uint32_t tls_type_val = static_cast(tls_type) << 12; VariousFlags |= tls_type_val; } + +String ControllerSettingsStruct::getCertificateFilename() const +{ + String certFile = HostName; + if (certFile.isEmpty()) { + certFile = F(""); + } + + switch (TLStype()) { + case TLS_types::NoTLS: + case TLS_types::TLS_insecure: + return EMPTY_STRING; + case TLS_types::TLS_PSK: + certFile += F(".psk"); + break; + /* + case TLS_types::TLS_CA_CLI_CERT: + certFile += F(".caclicert"); + break; + */ + case TLS_types::TLS_CA_CERT: + certFile += F(".cacert"); + break; + } + + return certFile; +} \ No newline at end of file diff --git a/src/src/DataStructs/ControllerSettingsStruct.h b/src/src/DataStructs/ControllerSettingsStruct.h index 3fcc8d1b49..1c021015b7 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.h +++ b/src/src/DataStructs/ControllerSettingsStruct.h @@ -151,6 +151,8 @@ struct ControllerSettingsStruct TLS_types TLStype() const; void TLStype(TLS_types tls_type); + + String getCertificateFilename() const; boolean UseDNS; diff --git a/src/src/DataTypes/TLS_types.h b/src/src/DataTypes/TLS_types.h index 8cf0e257d5..a8aec9aa14 100644 --- a/src/src/DataTypes/TLS_types.h +++ b/src/src/DataTypes/TLS_types.h @@ -7,11 +7,11 @@ // Value is stored, so do not change assigned integer values. enum class TLS_types { - NoTLS = 0, // Do not use encryption - TLS_PSK = 1, // Pre-Shared-Key - TLS_CA_CERT = 2, // Validate server certificate against known CA -//TLS_CA_CLI_CERT = 3, // TLS_CA_CERT + supply client certificate for authentication - TLS_insecure = 7 // Set as last option, do not check supplied certificate. Ideal for man-in-the-middle attack. + NoTLS = 0, // Do not use encryption + TLS_PSK = 1, // Pre-Shared-Key + TLS_CA_CERT = 2, // Validate server certificate against known CA +//TLS_CA_CLI_CERT = 3, // TLS_CA_CERT + supply client certificate for authentication + TLS_insecure = 0xF // Set as last option, do not check supplied certificate. Ideal for man-in-the-middle attack. }; const __FlashStringHelper* toString(TLS_types tls_type); diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 59b51f94ec..5449783e7c 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -211,13 +211,28 @@ bool MQTTConnect(controllerIndex_t controller_idx) } case TLS_types::TLS_CA_CERT: { - #ifdef ESP32 - mqtt_tls.setCACert(mqtt_rootCA); - #endif - #ifdef ESP8266 - mqtt_X509List.append(mqtt_rootCA); - mqtt_tls.setTrustAnchors(&mqtt_X509List); - #endif + const String certFile = ControllerSettings.getCertificateFilename(); + const size_t size = fileSize(certFile); + if (size > 0) { + if (mqtt_rootCA != nullptr) { + free(mqtt_rootCA); + } + mqtt_rootCA = (char*)malloc(size + 1); + if (mqtt_rootCA != nullptr) { + LoadFromFile(certFile.c_str(), 0, (uint8_t*)mqtt_rootCA, size); + mqtt_rootCA[size] = '\0'; + } + } + + if (mqtt_rootCA != nullptr) { + #ifdef ESP32 + mqtt_tls.setCACert(mqtt_rootCA); + #endif + #ifdef ESP8266 + mqtt_X509List.append(mqtt_rootCA); + mqtt_tls.setTrustAnchors(&mqtt_X509List); + #endif + } break; } /* diff --git a/src/src/Globals/MQTT.cpp b/src/src/Globals/MQTT.cpp index 10efc391c9..3c0c169754 100644 --- a/src/src/Globals/MQTT.cpp +++ b/src/src/Globals/MQTT.cpp @@ -18,7 +18,7 @@ WiFiClientSecure mqtt_tls; BearSSL::WiFiClientSecure mqtt_tls; BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 -const char *mqtt_rootCA = nullptr; +char *mqtt_rootCA = nullptr; # endif // ifdef USE_MQTT_TLS PubSubClient MQTTclient(mqtt); diff --git a/src/src/Globals/MQTT.h b/src/src/Globals/MQTT.h index af673b4e6f..00b7ebd266 100644 --- a/src/src/Globals/MQTT.h +++ b/src/src/Globals/MQTT.h @@ -28,7 +28,7 @@ extern BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 -extern const char *mqtt_rootCA; +extern char *mqtt_rootCA; # endif // ifdef USE_MQTT_TLS extern PubSubClient MQTTclient; extern bool MQTTclient_should_reconnect; diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 3201a6b441..4293e7c2c6 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -125,11 +125,19 @@ bool fileExists(const String& fname) { const String patched_fname = patch_fname(fname); auto search = Cache.fileExistsMap.find(patched_fname); if (search != Cache.fileExistsMap.end()) { - return search->second; + return search->second >= 0; + } + int size = -1; + if (ESPEASY_FS.exists(patched_fname)) { + size = 0; + fs::File f = ESPEASY_FS.open(patched_fname, "r"); + if (f) { + size = f.size(); + f.close(); + } } - bool res = ESPEASY_FS.exists(patched_fname); - Cache.fileExistsMap[patched_fname] = res; - return res; + Cache.fileExistsMap[patched_fname] = size; + return size; } fs::File tryOpenFile(const String& fname, const String& mode) { @@ -1252,6 +1260,52 @@ String LoadFromFile(const char *fname, int offset, uint8_t *memAddress, int data return String(); } +String LoadFromFile(const char *fname, String& data, int offset) +{ + fs::File f = tryOpenFile(fname, "r"); + SPIFFS_CHECK(f, fname); + #ifndef BUILD_NO_DEBUG + String log = F("LoadFromFile: "); + log += fname; + #else + String log = F("Load error"); + #endif + + if (!f || offset < 0 || (offset >= f.size())) { + #ifndef BUILD_NO_DEBUG + log += F(" ERROR, invalid position in file"); + #endif + addLog(LOG_LEVEL_ERROR, log); + return log; + } + delay(0); + START_TIMER; + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("LoadFromFile")); + #endif + + SPIFFS_CHECK(f.seek(offset, fs::SeekSet), fname); + if (f) { + if (!data.reserve(f.size() - offset)) { + #ifndef BUILD_NO_DEBUG + log += F(" ERROR, Out of memory"); + #endif + addLog(LOG_LEVEL_ERROR, log); + f.close(); + return log; + } + + while (f.available()) { data += (char)f.read(); } + f.close(); + } + + + STOP_TIMER(LOADFILE_STATS); + delay(0); + + return String(); +} + /********************************************************************************************\ Wrapper functions to handle errors in accessing settings \*********************************************************************************************/ diff --git a/src/src/Helpers/ESPEasy_Storage.h b/src/src/Helpers/ESPEasy_Storage.h index c9d88ceac6..c079c4350d 100644 --- a/src/src/Helpers/ESPEasy_Storage.h +++ b/src/src/Helpers/ESPEasy_Storage.h @@ -29,6 +29,8 @@ String appendToFile(const String& fname, const uint8_t *data, unsigned int size) bool fileExists(const String& fname); +int fileSize(const String& fname); + fs::File tryOpenFile(const String& fname, const String& mode); bool tryRenameFile(const String& fname_old, const String& fname_new); @@ -198,6 +200,8 @@ String ClearInFile(const char *fname, int index, int datasize); \*********************************************************************************************/ String LoadFromFile(const char *fname, int offset, uint8_t *memAddress, int datasize); +String LoadFromFile(const char *fname, String& data, int offset = 0); + /********************************************************************************************\ Wrapper functions to handle errors in accessing settings \*********************************************************************************************/ diff --git a/src/src/Helpers/_CPlugin_Helper_webform.cpp b/src/src/Helpers/_CPlugin_Helper_webform.cpp index 09b0c0d5e4..ebc493a272 100644 --- a/src/src/Helpers/_CPlugin_Helper_webform.cpp +++ b/src/src/Helpers/_CPlugin_Helper_webform.cpp @@ -162,17 +162,31 @@ void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettin const __FlashStringHelper * options[NR_MQTT_TLS_TYPES] = { toString(TLS_types::NoTLS), // toString(TLS_types::TLS_PSK), -// toString(TLS_types::TLS_CA_CERT), + toString(TLS_types::TLS_CA_CERT), toString(TLS_types::TLS_insecure) }; const int indices[NR_MQTT_TLS_TYPES] = { static_cast(TLS_types::NoTLS), // static_cast(TLS_types::TLS_PSK), -// static_cast(TLS_types::TLS_CA_CERT), + static_cast(TLS_types::TLS_CA_CERT), static_cast(TLS_types::TLS_insecure) }; addFormSelector(displayName, internalName, NR_MQTT_TLS_TYPES, options, indices, choice, true); #undef NR_MQTT_TLS_TYPES + const String certFile = ControllerSettings.getCertificateFilename(); + if (!certFile.isEmpty()) + { + const String certFile = ControllerSettings.getCertificateFilename(); + String note = F("Certificate or PSK must be stored on the filesystem in "); + note += certFile; + note += F(" "); + if (fileExists(certFile)) { + note += F("(File exists)"); + } else { + note += F("(Not found)"); + } + addFormNote(note); + } #endif break; } diff --git a/src/src/WebServer/WebServer.cpp b/src/src/WebServer/WebServer.cpp index 2e81800d1c..3dfe586618 100644 --- a/src/src/WebServer/WebServer.cpp +++ b/src/src/WebServer/WebServer.cpp @@ -91,7 +91,6 @@ void sendHeadandTail(const __FlashStringHelper * tmplName, boolean Tail, boolean String fileName = tmplName; fileName += F(".htm"); - fs::File f = tryOpenFile(fileName, "r"); WebTemplateParser templateParser(Tail, rebooting); if (f) { From 375391741b62d3baa44de959d0bc6ffce4979334 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 01:34:22 +0100 Subject: [PATCH 022/100] [Web] Allow to stream from file system (e.g. CSS inline) This may prevent additional calls to load the CSS from the file system in a separate HTTP GET call and also not loading the file into memory when streaming. --- src/src/WebServer/LoadFromFS.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/src/WebServer/LoadFromFS.cpp b/src/src/WebServer/LoadFromFS.cpp index 81dd288f2a..e10e71de59 100644 --- a/src/src/WebServer/LoadFromFS.cpp +++ b/src/src/WebServer/LoadFromFS.cpp @@ -176,6 +176,10 @@ size_t streamFromFS(String path, bool htmlEscape) { available = 0; } } + + while (f.available()) { + addHtml((char)f.read()); + } statusLED(true); f.close(); From 0567e79c92fc980ea66990a21a8e9d96aeb92f86 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 01:36:21 +0100 Subject: [PATCH 023/100] [MQTT TLS] Patch CA root cert to match strict layout Making it easier for users to copy/paste certificate code into a file. It will be patched at load from the file system. --- src/src/Helpers/ESPEasy_Storage.cpp | 131 +++++++++++++++++++++++++++- src/src/Helpers/ESPEasy_Storage.h | 7 ++ 2 files changed, 137 insertions(+), 1 deletion(-) diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 4293e7c2c6..b461bd914c 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -1023,6 +1023,135 @@ String LoadNotificationSettings(int NotificationIndex, uint8_t *memAddress, int return LoadFromFile(SettingsType::Enum::NotificationSettings_Type, NotificationIndex, memAddress, datasize); } + +/********************************************************************************************\ + Handle certificate files on the file system. + The content will be stripped from unusable character like quotes, spaces etc. + \*********************************************************************************************/ +static inline bool is_base64(char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +bool cleanupCertificate(String & certificate, bool &changed) +{ + changed = false; + // "-----BEGIN CERTIFICATE-----" positions in dash_pos[0] and dash_pos[1] + // "-----END CERTIFICATE-----" positions in dash_pos[2] and dash_pos[3] + int dash_pos[4] = { 0 }; + int last_pos = 0; + for (int i = 0; i < 4 && last_pos != -1; ++i) { + dash_pos[i] = certificate.indexOf(F("-----"), last_pos); + last_pos = dash_pos[i] + 5; + addLog(LOG_LEVEL_INFO, String(F(" dash_pos: ")) + String(dash_pos[i])); + } + if (last_pos == -1) return false; + + int read_pos = dash_pos[1] + 5; // next char after "-----BEGIN CERTIFICATE-----" + String newCert; + newCert.reserve((dash_pos[3] + 6) - dash_pos[0]); + + // "-----BEGIN CERTIFICATE-----" + newCert += certificate.substring(dash_pos[0], read_pos); + + char last_char = certificate[read_pos - 1]; + for (; read_pos < dash_pos[2]; ++read_pos) { + const char c = certificate[read_pos]; + if ((c == 'n' && last_char == '\\') || (c == '\n')) { + if (!newCert.endsWith(String('\n'))) { + newCert += '\n'; + } + } else if (is_base64(c) || c == '=') { + newCert += c; + } + last_char = c; + } + + // "-----END CERTIFICATE-----" + newCert += certificate.substring(dash_pos[2], dash_pos[3] + 5); + newCert += '\n'; + + changed = !certificate.equals(newCert); + certificate = std::move(newCert); + return true; +} + + +String SaveCertificate(const String& fname, const String& certificate) +{ + return SaveToFile(fname.c_str(), 0, (const uint8_t *)certificate.c_str(), certificate.length() + 1); +} + +String LoadCertificate(const String& fname, String& certificate) +{ + bool changed = false; + if (fileExists(fname)) { + fs::File f = tryOpenFile(fname, "r"); + SPIFFS_CHECK(f, fname.c_str()); + #ifndef BUILD_NO_DEBUG + String log = F("LoadCertificate: "); + log += fname; + #else + String log = F("LoadCertificate error"); + #endif + + certificate.clear(); + + if (!certificate.reserve(f.size())) { + #ifndef BUILD_NO_DEBUG + log += F(" ERROR, Out of memory"); + #endif + addLog(LOG_LEVEL_ERROR, log); + f.close(); + return log; + } + bool done = false; + while (f.available() && !done) { + const char c = (char)f.read(); + if (c == '\0') { + done = true; + } else { + certificate += c; + } + } + f.close(); + + String analyse = F("Cleanup: Before: "); + analyse += certificate.length(); + analyse += F(" After: "); + + if (!cleanupCertificate(certificate, changed)) { + certificate.clear(); + #ifndef BUILD_NO_DEBUG + log += F(" ERROR, Invalid certificate format"); + #endif + addLog(LOG_LEVEL_ERROR, log); + return log; + } else if (changed) { + //return SaveCertificate(fname, certificate); + } +// addLog(LOG_LEVEL_INFO, F("After")); +// addLog(LOG_LEVEL_INFO, certificate); + static int previousFree = FreeMem(); + const int freemem = FreeMem(); + + analyse += certificate.length(); + analyse += changed ? F(" changed") : F(" same"); + analyse += F(" free memory: "); + analyse += freemem; + analyse += F(" largest free block: "); + analyse += getMaxFreeBlock(); + + analyse += F(" Difference: "); + analyse += previousFree - freemem; + + addLog(LOG_LEVEL_INFO, analyse); + previousFree = freemem; + } + + return EMPTY_STRING; +} + + /********************************************************************************************\ Init a file with zeros on file system \*********************************************************************************************/ @@ -1271,7 +1400,7 @@ String LoadFromFile(const char *fname, String& data, int offset) String log = F("Load error"); #endif - if (!f || offset < 0 || (offset >= f.size())) { + if (!f || offset < 0 || (offset >= static_cast(f.size()))) { #ifndef BUILD_NO_DEBUG log += F(" ERROR, invalid position in file"); #endif diff --git a/src/src/Helpers/ESPEasy_Storage.h b/src/src/Helpers/ESPEasy_Storage.h index c079c4350d..620c041bbe 100644 --- a/src/src/Helpers/ESPEasy_Storage.h +++ b/src/src/Helpers/ESPEasy_Storage.h @@ -172,6 +172,13 @@ String SaveNotificationSettings(int NotificationIndex, const uint8_t *memAddress \*********************************************************************************************/ String LoadNotificationSettings(int NotificationIndex, uint8_t *memAddress, int datasize); +/********************************************************************************************\ + Handle certificate files on the file system. + The content will be stripped from unusable character like quotes, spaces etc. + \*********************************************************************************************/ +String SaveCertificate(const String& fname, const String& certificate); +String LoadCertificate(const String& fname, String& certificate); + /********************************************************************************************\ Init a file with zeros on file system From 5ab83c56f6c8c0d772a184975d348f4f3832c349 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 01:38:35 +0100 Subject: [PATCH 024/100] [MQTT TLS] Work-around for memory leak in MbedTLS (not finished) There is a memory leak in Mbed TLS, when connection failed. For example when using a CA root certificate which does not match the certificate of the host we're connecting to. This will take about 1880 bytes of memory on each attempt. Still a work-in-progress as it is not yet fixed. --- src/src/ESPEasyCore/Controller.cpp | 80 ++- src/src/Globals/MQTT.cpp | 6 +- src/src/Globals/MQTT.h | 10 +- src/src/Helpers/ESPEasy_WiFiClientSecure.cpp | 341 ++++++++++++ src/src/Helpers/ESPEasy_WiFiClientSecure.h | 113 ++++ src/src/Helpers/ESPEasy_ssl_client.cpp | 530 +++++++++++++++++++ src/src/Helpers/ESPEasy_ssl_client.h | 53 ++ 7 files changed, 1104 insertions(+), 29 deletions(-) create mode 100644 src/src/Helpers/ESPEasy_WiFiClientSecure.cpp create mode 100644 src/src/Helpers/ESPEasy_WiFiClientSecure.h create mode 100644 src/src/Helpers/ESPEasy_ssl_client.cpp create mode 100644 src/src/Helpers/ESPEasy_ssl_client.h diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 5449783e7c..fafa1f4717 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -25,6 +25,7 @@ #include "../Globals/Protocol.h" #include "../Helpers/_CPlugin_Helper.h" +#include "../Helpers/Memory.h" #include "../Helpers/Misc.h" #include "../Helpers/Network.h" #include "../Helpers/PeriodicalActions.h" @@ -184,6 +185,14 @@ bool MQTTConnect(controllerIndex_t controller_idx) if (MQTTclient.connected()) { MQTTclient.disconnect(); + # ifdef USE_MQTT_TLS + /* + if (mqtt_tls != nullptr) { + delete mqtt_tls; + mqtt_tls = nullptr; + } + */ + #endif } updateMQTTclient_connected(); @@ -197,6 +206,19 @@ bool MQTTConnect(controllerIndex_t controller_idx) mqtt_tls_last_errorstr = EMPTY_STRING; mqtt_tls_last_error = 0; const TLS_types TLS_type = ControllerSettings.TLStype(); + if (TLS_type != TLS_types::NoTLS) { + #ifdef ESP32 + mqtt_tls = new ESPEasy_WiFiClientSecure; + #endif + #ifdef ESP8266 + mqtt_tls = new BearSSL::WiFiClientSecure; + #endif + + if (mqtt_tls == nullptr) { + addLog(LOG_LEVEL_ERROR, F("MQTT : Could not create TLS client, out of memory")); + return false; + } + } switch(TLS_type) { case TLS_types::NoTLS: { @@ -206,31 +228,39 @@ bool MQTTConnect(controllerIndex_t controller_idx) } case TLS_types::TLS_PSK: { - //mqtt_tls.setPreSharedKey(const char *pskIdent, const char *psKey); // psKey in Hex + //mqtt_tls->setPreSharedKey(const char *pskIdent, const char *psKey); // psKey in Hex break; } case TLS_types::TLS_CA_CERT: { - const String certFile = ControllerSettings.getCertificateFilename(); - const size_t size = fileSize(certFile); - if (size > 0) { - if (mqtt_rootCA != nullptr) { - free(mqtt_rootCA); - } - mqtt_rootCA = (char*)malloc(size + 1); - if (mqtt_rootCA != nullptr) { - LoadFromFile(certFile.c_str(), 0, (uint8_t*)mqtt_rootCA, size); - mqtt_rootCA[size] = '\0'; - } +// mqtt_rootCA.clear(); + if (mqtt_rootCA.isEmpty()) + LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_rootCA); + + { + static int previousFree = FreeMem(); + const int freemem = FreeMem(); + + String analyse = F(" free memory: "); + analyse += freemem; + analyse += F(" largest free block: "); + analyse += getMaxFreeBlock(); + + analyse += F(" Difference: "); + analyse += previousFree - freemem; + + addLog(LOG_LEVEL_INFO, analyse); + previousFree = freemem; } - if (mqtt_rootCA != nullptr) { + + if (mqtt_rootCA.length() > 0) { #ifdef ESP32 - mqtt_tls.setCACert(mqtt_rootCA); + mqtt_tls->setCACert(mqtt_rootCA.c_str()); #endif #ifdef ESP8266 - mqtt_X509List.append(mqtt_rootCA); - mqtt_tls.setTrustAnchors(&mqtt_X509List); + mqtt_X509List.append(mqtt_rootCA.c_str()); + mqtt_tls->setTrustAnchors(&mqtt_X509List); #endif } break; @@ -238,22 +268,22 @@ bool MQTTConnect(controllerIndex_t controller_idx) /* case TLS_types::TLS_CA_CLI_CERT: { - //mqtt_tls.setCertificate(const char *client_ca); + //mqtt_tls->setCertificate(const char *client_ca); break; } */ case TLS_types::TLS_insecure: { - mqtt_tls.setInsecure(); + mqtt_tls->setInsecure(); break; } } if (TLS_type != TLS_types::NoTLS) { - mqtt_tls.setTimeout(ControllerSettings.ClientTimeout); + mqtt_tls->setTimeout(ControllerSettings.ClientTimeout); #ifdef ESP8266 - mqtt_tls.setBufferSizes(1024,1024); + mqtt_tls->setBufferSizes(1024,1024); #endif - MQTTclient.setClient(mqtt_tls); + MQTTclient.setClient(*mqtt_tls); if (mqttPort == 1883) { mqttPort = 8883; } @@ -319,10 +349,10 @@ bool MQTTConnect(controllerIndex_t controller_idx) { char buf[128] = {0}; #ifdef ESP8266 - mqtt_tls_last_error = mqtt_tls.getLastSSLError(buf,128); + mqtt_tls_last_error = mqtt_tls->getLastSSLError(buf,128); #endif #ifdef ESP32 - mqtt_tls_last_error = mqtt_tls.lastError(buf,128); + mqtt_tls_last_error = mqtt_tls->lastError(buf,128); #endif mqtt_tls_last_errorstr = buf; } @@ -341,6 +371,10 @@ bool MQTTConnect(controllerIndex_t controller_idx) #endif MQTTclient.disconnect(); + #ifdef USE_MQTT_TLS + mqtt_tls->stop(); + #endif + updateMQTTclient_connected(); return false; } diff --git a/src/src/Globals/MQTT.cpp b/src/src/Globals/MQTT.cpp index 3c0c169754..3434c4b42b 100644 --- a/src/src/Globals/MQTT.cpp +++ b/src/src/Globals/MQTT.cpp @@ -12,13 +12,13 @@ String mqtt_tls_last_errorstr; int32_t mqtt_tls_last_error = 0; # ifdef ESP32 -WiFiClientSecure mqtt_tls; +ESPEasy_WiFiClientSecure* mqtt_tls; # endif // ifdef ESP32 # ifdef ESP8266 -BearSSL::WiFiClientSecure mqtt_tls; +BearSSL::WiFiClientSecure* mqtt_tls; BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 -char *mqtt_rootCA = nullptr; +String mqtt_rootCA; # endif // ifdef USE_MQTT_TLS PubSubClient MQTTclient(mqtt); diff --git a/src/src/Globals/MQTT.h b/src/src/Globals/MQTT.h index 00b7ebd266..3cf7ff14b3 100644 --- a/src/src/Globals/MQTT.h +++ b/src/src/Globals/MQTT.h @@ -11,7 +11,11 @@ # include # ifdef USE_MQTT_TLS +# ifdef ESP32 +# include "../Helpers/ESPEasy_WiFiClientSecure.h" +# else # include +# endif # endif // ifdef USE_MQTT_TLS // MQTT client @@ -20,15 +24,15 @@ extern WiFiClient mqtt; extern String mqtt_tls_last_errorstr; extern int32_t mqtt_tls_last_error; # ifdef ESP32 -extern WiFiClientSecure mqtt_tls; +extern ESPEasy_WiFiClientSecure* mqtt_tls; # endif // ifdef ESP32 # ifdef ESP8266 -extern BearSSL::WiFiClientSecure mqtt_tls; +extern BearSSL::WiFiClientSecure* mqtt_tls; extern BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 -extern char *mqtt_rootCA; +extern String mqtt_rootCA; # endif // ifdef USE_MQTT_TLS extern PubSubClient MQTTclient; extern bool MQTTclient_should_reconnect; diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp new file mode 100644 index 0000000000..f84c5aeb40 --- /dev/null +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp @@ -0,0 +1,341 @@ +#include "../Helpers/ESPEasy_WiFiClientSecure.h" + +/* + ESPEasy_WiFiClientSecure.cpp - Client Secure class for ESP32 + Copyright (c) 2016 Hristo Gochkov All right reserved. + Additions Copyright (C) 2017 Evandro Luis Copercini. + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include + +#undef connect +#undef write +#undef read + + +ESPEasy_WiFiClientSecure::ESPEasy_WiFiClientSecure() +{ + _connected = false; + + sslclient = new ESPEasy_sslclient_context; + ssl_init(sslclient); + sslclient->socket = -1; + sslclient->handshake_timeout = 120000; + _use_insecure = false; + _CA_cert = NULL; + _cert = NULL; + _private_key = NULL; + _pskIdent = NULL; + _psKey = NULL; + next = NULL; +} + + +ESPEasy_WiFiClientSecure::ESPEasy_WiFiClientSecure(int sock) +{ + _connected = false; + _timeout = 0; + + sslclient = new ESPEasy_sslclient_context; + ssl_init(sslclient); + sslclient->socket = sock; + sslclient->handshake_timeout = 120000; + + if (sock >= 0) { + _connected = true; + } + + _CA_cert = NULL; + _cert = NULL; + _private_key = NULL; + _pskIdent = NULL; + _psKey = NULL; + next = NULL; +} + +ESPEasy_WiFiClientSecure::~ESPEasy_WiFiClientSecure() +{ + stop(); + delete sslclient; +} + +ESPEasy_WiFiClientSecure &ESPEasy_WiFiClientSecure::operator=(const ESPEasy_WiFiClientSecure &other) +{ + stop(); + sslclient->socket = other.sslclient->socket; + _connected = other._connected; + return *this; +} + +void ESPEasy_WiFiClientSecure::stop() +{ + if (sslclient->socket >= 0) { + close(sslclient->socket); + sslclient->socket = -1; + _connected = false; + _peek = -1; + } + stop_ssl_socket(sslclient, _CA_cert, _cert, _private_key); +} + +int ESPEasy_WiFiClientSecure::connect(IPAddress ip, uint16_t port) +{ + if (_pskIdent && _psKey) + return connect(ip, port, _pskIdent, _psKey); + return connect(ip, port, _CA_cert, _cert, _private_key); +} + +int ESPEasy_WiFiClientSecure::connect(IPAddress ip, uint16_t port, int32_t timeout){ + _timeout = timeout; + return connect(ip, port); +} + +int ESPEasy_WiFiClientSecure::connect(const char *host, uint16_t port) +{ + if (_pskIdent && _psKey) + return connect(host, port, _pskIdent, _psKey); + return connect(host, port, _CA_cert, _cert, _private_key); +} + +int ESPEasy_WiFiClientSecure::connect(const char *host, uint16_t port, int32_t timeout){ + _timeout = timeout; + return connect(host, port); +} + +int ESPEasy_WiFiClientSecure::connect(IPAddress ip, uint16_t port, const char *CA_cert, const char *cert, const char *private_key) +{ + return connect(ip.toString().c_str(), port, CA_cert, cert, private_key); +} + +int ESPEasy_WiFiClientSecure::connect(const char *host, uint16_t port, const char *CA_cert, const char *cert, const char *private_key) +{ + if(_timeout > 0){ + sslclient->handshake_timeout = _timeout; + } + int ret = start_ssl_client(sslclient, host, port, _timeout, CA_cert, cert, private_key, NULL, NULL, _use_insecure); + _lastError = ret; + if (ret < 0) { + log_e("start_ssl_client: %d", ret); + stop(); + return 0; + } + _connected = true; + return 1; +} + +int ESPEasy_WiFiClientSecure::connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psKey) { + return connect(ip.toString().c_str(), port, pskIdent, psKey); +} + +int ESPEasy_WiFiClientSecure::connect(const char *host, uint16_t port, const char *pskIdent, const char *psKey) { + log_v("start_ssl_client with PSK"); + if(_timeout > 0){ + sslclient->handshake_timeout = _timeout; + } + int ret = start_ssl_client(sslclient, host, port, _timeout, NULL, NULL, NULL, pskIdent, psKey, _use_insecure); + _lastError = ret; + if (ret < 0) { + log_e("start_ssl_client: %d", ret); + stop(); + return 0; + } + _connected = true; + return 1; +} + +int ESPEasy_WiFiClientSecure::peek(){ + if(_peek >= 0){ + return _peek; + } + _peek = timedRead(); + return _peek; +} + +size_t ESPEasy_WiFiClientSecure::write(uint8_t data) +{ + return write(&data, 1); +} + +int ESPEasy_WiFiClientSecure::read() +{ + uint8_t data = -1; + int res = read(&data, 1); + if (res < 0) { + return res; + } + return data; +} + +size_t ESPEasy_WiFiClientSecure::write(const uint8_t *buf, size_t size) +{ + if (!_connected) { + return 0; + } + int res = send_ssl_data(sslclient, buf, size); + if (res < 0) { + stop(); + res = 0; + } + return res; +} + +int ESPEasy_WiFiClientSecure::read(uint8_t *buf, size_t size) +{ + int peeked = 0; + int avail = available(); + if ((!buf && size) || avail <= 0) { + return -1; + } + if(!size){ + return 0; + } + if(_peek >= 0){ + buf[0] = _peek; + _peek = -1; + size--; + avail--; + if(!size || !avail){ + return 1; + } + buf++; + peeked = 1; + } + + int res = get_ssl_receive(sslclient, buf, size); + if (res < 0) { + stop(); + return peeked?peeked:res; + } + return res + peeked; +} + +int ESPEasy_WiFiClientSecure::available() +{ + int peeked = (_peek >= 0); + if (!_connected) { + return peeked; + } + int res = data_to_read(sslclient); + if (res < 0) { + stop(); + return peeked?peeked:res; + } + return res+peeked; +} + +uint8_t ESPEasy_WiFiClientSecure::connected() +{ + uint8_t dummy = 0; + read(&dummy, 0); + + return _connected; +} + +void ESPEasy_WiFiClientSecure::setInsecure() +{ + _CA_cert = NULL; + _cert = NULL; + _private_key = NULL; + _pskIdent = NULL; + _psKey = NULL; + _use_insecure = true; +} + +void ESPEasy_WiFiClientSecure::setCACert (const char *rootCA) +{ + _CA_cert = rootCA; +} + +void ESPEasy_WiFiClientSecure::setCertificate (const char *client_ca) +{ + _cert = client_ca; +} + +void ESPEasy_WiFiClientSecure::setPrivateKey (const char *private_key) +{ + _private_key = private_key; +} + +void ESPEasy_WiFiClientSecure::setPreSharedKey(const char *pskIdent, const char *psKey) { + _pskIdent = pskIdent; + _psKey = psKey; +} + +bool ESPEasy_WiFiClientSecure::verify(const char* fp, const char* domain_name) +{ + if (!sslclient) + return false; + + return verify_ssl_fingerprint(sslclient, fp, domain_name); +} + +char *ESPEasy_WiFiClientSecure::_streamLoad(Stream& stream, size_t size) { + char *dest = (char*)malloc(size+1); + if (!dest) { + return nullptr; + } + if (size != stream.readBytes(dest, size)) { + free(dest); + dest = nullptr; + return nullptr; + } + dest[size] = '\0'; + return dest; +} + +bool ESPEasy_WiFiClientSecure::loadCACert(Stream& stream, size_t size) { + char *dest = _streamLoad(stream, size); + bool ret = false; + if (dest) { + setCACert(dest); + ret = true; + } + return ret; +} + +bool ESPEasy_WiFiClientSecure::loadCertificate(Stream& stream, size_t size) { + char *dest = _streamLoad(stream, size); + bool ret = false; + if (dest) { + setCertificate(dest); + ret = true; + } + return ret; +} + +bool ESPEasy_WiFiClientSecure::loadPrivateKey(Stream& stream, size_t size) { + char *dest = _streamLoad(stream, size); + bool ret = false; + if (dest) { + setPrivateKey(dest); + ret = true; + } + return ret; +} + +int ESPEasy_WiFiClientSecure::lastError(char *buf, const size_t size) +{ + if (!_lastError) { + return 0; + } + mbedtls_strerror(_lastError, buf, size); + return _lastError; +} + +void ESPEasy_WiFiClientSecure::setHandshakeTimeout(unsigned long handshake_timeout) +{ + sslclient->handshake_timeout = handshake_timeout * 1000; +} \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.h b/src/src/Helpers/ESPEasy_WiFiClientSecure.h new file mode 100644 index 0000000000..08e129446b --- /dev/null +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.h @@ -0,0 +1,113 @@ +/* + ESPEasy_WiFiClientSecure.h - Base class that provides Client SSL to ESP32 + Copyright (c) 2011 Adrian McEwen. All right reserved. + Additions Copyright (C) 2017 Evandro Luis Copercini. + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + Almost literal copy of https://github.com/brokentoaster/arduino-esp32/blob/master/libraries/WiFiClientSecure/src/WiFiClientSecure.h + Changed into "ESPEasy" version to incorporate some fixes + for memory leaks not yet present in the "older" core versions. +*/ + +#ifndef ESPEASY_WiFiClientSecure_h +#define ESPEASY_WiFiClientSecure_h +#include +#include +#include +#include "../Helpers/ESPEasy_ssl_client.h" + +class ESPEasy_WiFiClientSecure : public WiFiClient +{ +protected: + ESPEasy_sslclient_context *sslclient; + + int _lastError = 0; + int _peek = -1; + int _timeout = 0; + bool _use_insecure; + const char *_CA_cert; + const char *_cert; + const char *_private_key; + const char *_pskIdent; // identity for PSK cipher suites + const char *_psKey; // key in hex for PSK cipher suites + +public: + ESPEasy_WiFiClientSecure *next; + ESPEasy_WiFiClientSecure(); + ESPEasy_WiFiClientSecure(int socket); + ~ESPEasy_WiFiClientSecure(); + int connect(IPAddress ip, uint16_t port); + int connect(IPAddress ip, uint16_t port, int32_t timeout); + int connect(const char *host, uint16_t port); + int connect(const char *host, uint16_t port, int32_t timeout); + int connect(IPAddress ip, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key); + int connect(const char *host, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key); + int connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psKey); + int connect(const char *host, uint16_t port, const char *pskIdent, const char *psKey); + int peek(); + size_t write(uint8_t data); + size_t write(const uint8_t *buf, size_t size); + int available(); + int read(); + int read(uint8_t *buf, size_t size); + void flush() {} + void stop(); + uint8_t connected(); + int lastError(char *buf, const size_t size); + void setInsecure(); // Don't validate the chain, just accept whatever is given. VERY INSECURE! + void setPreSharedKey(const char *pskIdent, const char *psKey); // psKey in Hex + void setCACert(const char *rootCA); + void setCertificate(const char *client_ca); + void setPrivateKey (const char *private_key); + bool loadCACert(Stream& stream, size_t size); + bool loadCertificate(Stream& stream, size_t size); + bool loadPrivateKey(Stream& stream, size_t size); + bool verify(const char* fingerprint, const char* domain_name); + void setHandshakeTimeout(unsigned long handshake_timeout); + + int setTimeout(uint32_t seconds){ return 0; } + + operator bool() + { + return connected(); + } + ESPEasy_WiFiClientSecure &operator=(const ESPEasy_WiFiClientSecure &other); + bool operator==(const bool value) + { + return bool() == value; + } + bool operator!=(const bool value) + { + return bool() != value; + } + bool operator==(const ESPEasy_WiFiClientSecure &); + bool operator!=(const ESPEasy_WiFiClientSecure &rhs) + { + return !this->operator==(rhs); + }; + + int socket() + { + return sslclient->socket = -1; + } + +private: + char *_streamLoad(Stream& stream, size_t size); + + //friend class WiFiServer; + using Print::write; +}; + +#endif \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_ssl_client.cpp b/src/src/Helpers/ESPEasy_ssl_client.cpp new file mode 100644 index 0000000000..4ff9996b3b --- /dev/null +++ b/src/src/Helpers/ESPEasy_ssl_client.cpp @@ -0,0 +1,530 @@ +#include "../Helpers/ESPEasy_ssl_client.h" + +/* Provide SSL/TLS functions to ESP32 with Arduino IDE +* +* Adapted from the ssl_client1 example of mbedtls. +* +* Original Copyright (C) 2006-2015, ARM Limited, All Rights Reserved, Apache 2.0 License. +* Additions Copyright (C) 2017 Evandro Luis Copercini, Apache 2.0 License. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED +# error "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher" +#endif + +const char *ESPEasy_pers = "esp32-tls"; + +static int _handle_error(int err, const char * file, int line) +{ + if(err == -30848){ + return err; + } +#ifdef MBEDTLS_ERROR_C + char error_buf[100]; + mbedtls_strerror(err, error_buf, 100); + log_e("[%s():%d]: (%d) %s", file, line, err, error_buf); +#else + log_e("[%s():%d]: code %d", file, line, err); +#endif + return err; +} + +#define handle_error(e) _handle_error(e, __FUNCTION__, __LINE__) + +ESPEasy_sslclient_context::ESPEasy_sslclient_context() +{ + memset(&ssl_ctx, 0, sizeof(ssl_ctx)); + memset(&ssl_conf, 0, sizeof(ssl_conf)); + memset(&drbg_ctx, 0, sizeof(drbg_ctx)); + memset(&entropy_ctx, 0, sizeof(entropy_ctx)); + memset(&ca_cert, 0, sizeof(ca_cert)); + memset(&client_cert, 0, sizeof(client_cert)); + memset(&client_key, 0, sizeof(client_key)); +} + + +ESPEasy_sslclient_context::~ESPEasy_sslclient_context() +{ + free_ca_cert(); + free_client_cert(); + free_client_key(); +} + + +void ESPEasy_sslclient_context::free_ca_cert() +{ +// if (ca_cert.p != nullptr) { + if (ca_cert_init) { + ca_cert_init = false; + } + mbedtls_x509_crt_free(&ca_cert); +// } +} + +void ESPEasy_sslclient_context::free_client_cert() +{ + if (client_cert_init) { + client_cert_init = false; + } + mbedtls_x509_crt_free(&client_cert); +// } +} + +void ESPEasy_sslclient_context::free_client_key() +{ + if (client_key_init) { + client_key_init = false; + } + mbedtls_pk_free(&client_key); +// } +} + + +void ssl_init(ESPEasy_sslclient_context *ssl_client) +{ + mbedtls_ssl_free(&ssl_client->ssl_ctx); + mbedtls_ssl_config_free(&ssl_client->ssl_conf); + mbedtls_ctr_drbg_free(&ssl_client->drbg_ctx); + + mbedtls_ssl_init(&ssl_client->ssl_ctx); + mbedtls_ssl_config_init(&ssl_client->ssl_conf); + mbedtls_ctr_drbg_init(&ssl_client->drbg_ctx); +} + + +int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure) +{ + char buf[512]; + int ret, flags; + int enable = 1; + log_v("Free internal heap before TLS %u", ESP.getFreeHeap()); + + if (rootCABuff == NULL && pskIdent == NULL && psKey == NULL && !insecure) { + return -1; + } + + log_v("Starting socket"); + ssl_client->socket = -1; + + ssl_client->socket = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (ssl_client->socket < 0) { + log_e("ERROR opening socket"); + return ssl_client->socket; + } + + IPAddress srv((uint32_t)0); + if(!WiFiGenericClass::hostByName(host, srv)){ + return -1; + } + + struct sockaddr_in serv_addr; + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = srv; + serv_addr.sin_port = htons(port); + + if (lwip_connect(ssl_client->socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == 0) { + if(timeout <= 0){ + timeout = 30000; // Milli seconds. + } + timeval so_timeout = { .tv_sec = timeout / 1000, .tv_usec = (timeout % 1000) * 1000 }; + +#define ROE(x,msg) { if (((x)<0)) { log_e("LWIP Socket config of " msg " failed."); return -1; }} + ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_RCVTIMEO, &so_timeout, sizeof(so_timeout)),"SO_RCVTIMEO"); + ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_SNDTIMEO, &so_timeout, sizeof(so_timeout)),"SO_SNDTIMEO"); + + ROE(lwip_setsockopt(ssl_client->socket, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)),"TCP_NODELAY"); + ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)),"SO_KEEPALIVE"); + } else { + log_e("Connect to Server failed!"); + return -1; + } + + fcntl( ssl_client->socket, F_SETFL, fcntl( ssl_client->socket, F_GETFL, 0 ) | O_NONBLOCK ); + + log_v("Seeding the random number generator"); + mbedtls_entropy_free(&ssl_client->entropy_ctx); + mbedtls_entropy_init(&ssl_client->entropy_ctx); + + ret = mbedtls_ctr_drbg_seed(&ssl_client->drbg_ctx, mbedtls_entropy_func, + &ssl_client->entropy_ctx, (const unsigned char *) ESPEasy_pers, strlen(ESPEasy_pers)); + if (ret < 0) { + return handle_error(ret); + } + + log_v("Setting up the SSL/TLS structure..."); + + if ((ret = mbedtls_ssl_config_defaults(&ssl_client->ssl_conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + return handle_error(ret); + } + + // MBEDTLS_SSL_VERIFY_REQUIRED if a CA certificate is defined on Arduino IDE and + // MBEDTLS_SSL_VERIFY_NONE if not. + + if (insecure) { + mbedtls_ssl_conf_authmode(&ssl_client->ssl_conf, MBEDTLS_SSL_VERIFY_NONE); + log_i("WARNING: Skipping SSL Verification. INSECURE!"); + } else if (rootCABuff != NULL) { + log_v("Loading CA cert"); + mbedtls_x509_crt_init(&ssl_client->ca_cert); + ssl_client->ca_cert_init = true; + mbedtls_ssl_conf_authmode(&ssl_client->ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED); + ret = mbedtls_x509_crt_parse(&ssl_client->ca_cert, (const unsigned char *)rootCABuff, strlen(rootCABuff) + 1); + mbedtls_ssl_conf_ca_chain(&ssl_client->ssl_conf, &ssl_client->ca_cert, NULL); + //mbedtls_ssl_conf_verify(&ssl_client->ssl_ctx, my_verify, NULL ); + if (ret < 0) { + // free the ca_cert in the case parse failed, otherwise, the old ca_cert still in the heap memory, that lead to "out of memory" crash. + ssl_client->free_ca_cert(); + return handle_error(ret); + } + } else if (pskIdent != NULL && psKey != NULL) { + log_v("Setting up PSK"); + // convert PSK from hex to binary + if ((strlen(psKey) & 1) != 0 || strlen(psKey) > 2*MBEDTLS_PSK_MAX_LEN) { + log_e("pre-shared key not valid hex or too long"); + return -1; + } + unsigned char psk[MBEDTLS_PSK_MAX_LEN]; + size_t psk_len = strlen(psKey)/2; + for (int j=0; j= '0' && c <= '9') c -= '0'; + else if (c >= 'A' && c <= 'F') c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') c -= 'a' - 10; + else return -1; + psk[j/2] = c<<4; + c = psKey[j+1]; + if (c >= '0' && c <= '9') c -= '0'; + else if (c >= 'A' && c <= 'F') c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') c -= 'a' - 10; + else return -1; + psk[j/2] |= c; + } + // set mbedtls config + ret = mbedtls_ssl_conf_psk(&ssl_client->ssl_conf, psk, psk_len, + (const unsigned char *)pskIdent, strlen(pskIdent)); + if (ret != 0) { + log_e("mbedtls_ssl_conf_psk returned %d", ret); + return handle_error(ret); + } + } else { + return -1; + } + + if (!insecure && cli_cert != NULL && cli_key != NULL) { + mbedtls_x509_crt_init(&ssl_client->client_cert); + mbedtls_pk_init(&ssl_client->client_key); + + + log_v("Loading CRT cert"); + + ret = mbedtls_x509_crt_parse(&ssl_client->client_cert, (const unsigned char *)cli_cert, strlen(cli_cert) + 1); + ssl_client->client_cert_init = true; + if (ret < 0) { + // free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash. + ssl_client->free_client_cert(); + return handle_error(ret); + } + + log_v("Loading private key"); + ret = mbedtls_pk_parse_key(&ssl_client->client_key, (const unsigned char *)cli_key, strlen(cli_key) + 1, NULL, 0); + ssl_client->client_key_init = true; + + if (ret != 0) { + return handle_error(ret); + } + + mbedtls_ssl_conf_own_cert(&ssl_client->ssl_conf, &ssl_client->client_cert, &ssl_client->client_key); + } + + log_v("Setting hostname for TLS session..."); + + // Hostname set here should match CN in server certificate + if((ret = mbedtls_ssl_set_hostname(&ssl_client->ssl_ctx, host)) != 0){ + return handle_error(ret); + } + + mbedtls_ssl_conf_rng(&ssl_client->ssl_conf, mbedtls_ctr_drbg_random, &ssl_client->drbg_ctx); + + if ((ret = mbedtls_ssl_setup(&ssl_client->ssl_ctx, &ssl_client->ssl_conf)) != 0) { + return handle_error(ret); + } + + mbedtls_ssl_set_bio(&ssl_client->ssl_ctx, &ssl_client->socket, mbedtls_net_send, mbedtls_net_recv, NULL ); + + log_v("Performing the SSL/TLS handshake..."); + unsigned long handshake_start_time=millis(); + while ((ret = mbedtls_ssl_handshake(&ssl_client->ssl_ctx)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + // ++++++++++ ADDED TO FIX MEMORY LEAK ON FAILED CONNECTION ++++++++++ + ssl_client->free_client_key(); + ssl_client->free_ca_cert(); + ssl_client->free_client_cert(); + // ++++++++++ END ++++++++++ + return handle_error(ret); + } + if ((millis()-handshake_start_time) > ssl_client->handshake_timeout) { + // ++++++++++ ADDED TO FIX MEMORY LEAK ON FAILED CONNECTION ++++++++++ + ssl_client->free_client_key(); + ssl_client->free_ca_cert(); + ssl_client->free_client_cert(); + // ++++++++++ END ++++++++++ + return -1; + } + vTaskDelay(2);//2 ticks + } + + + if (cli_cert != NULL && cli_key != NULL) { + log_d("Protocol is %s Ciphersuite is %s", mbedtls_ssl_get_version(&ssl_client->ssl_ctx), mbedtls_ssl_get_ciphersuite(&ssl_client->ssl_ctx)); + if ((ret = mbedtls_ssl_get_record_expansion(&ssl_client->ssl_ctx)) >= 0) { + log_d("Record expansion is %d", ret); + } else { + log_w("Record expansion is unknown (compression)"); + } + } + + log_v("Verifying peer X.509 certificate..."); + + if ((flags = mbedtls_ssl_get_verify_result(&ssl_client->ssl_ctx)) != 0) { + memset(buf, 0, sizeof(buf)); + mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags); + log_e("Failed to verify peer certificate! verification info: %s", buf); + stop_ssl_socket(ssl_client, rootCABuff, cli_cert, cli_key); //It's not safe continue. + // ++++++++++ ADDED TO FIX MEMORY LEAK ON FAILED CONNECTION ++++++++++ + ssl_client->free_client_key(); + ssl_client->free_ca_cert(); + ssl_client->free_client_cert(); + // ++++++++++ END ++++++++++ + + return handle_error(ret); + } else { + log_v("Certificate verified."); + } + + ssl_client->free_ca_cert(); + ssl_client->free_client_cert(); + ssl_client->free_client_key(); + + log_v("Free internal heap after TLS %u", ESP.getFreeHeap()); + + return ssl_client->socket; +} + + +void stop_ssl_socket(ESPEasy_sslclient_context *ssl_client, const char *rootCABuff, const char *cli_cert, const char *cli_key) +{ + log_v("Cleaning SSL connection."); + + if (ssl_client->socket >= 0) { + close(ssl_client->socket); + ssl_client->socket = -1; + } + + mbedtls_ssl_free(&ssl_client->ssl_ctx); + mbedtls_ssl_config_free(&ssl_client->ssl_conf); + mbedtls_ctr_drbg_free(&ssl_client->drbg_ctx); + mbedtls_entropy_free(&ssl_client->entropy_ctx); + ssl_client->free_ca_cert(); + ssl_client->free_client_cert(); + ssl_client->free_client_key(); +} + + +int data_to_read(ESPEasy_sslclient_context *ssl_client) +{ + int ret, res; + ret = mbedtls_ssl_read(&ssl_client->ssl_ctx, NULL, 0); + //log_e("RET: %i",ret); //for low level debug + res = mbedtls_ssl_get_bytes_avail(&ssl_client->ssl_ctx); + //log_e("RES: %i",res); //for low level debug + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) { + return handle_error(ret); + } + + return res; +} + +int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, uint16_t len) +{ + log_v("Writing HTTP request with %d bytes...", len); //for low level debug + int ret = -1; + + if ((ret = mbedtls_ssl_write(&ssl_client->ssl_ctx, data, len)) <= 0){ + log_v("Handling error %d", ret); //for low level debug + return handle_error(ret); + } else{ + log_v("Returning with %d bytes written", ret); //for low level debug + } + + return ret; +} + +int get_ssl_receive(ESPEasy_sslclient_context *ssl_client, uint8_t *data, int length) +{ + //log_d( "Reading HTTP response..."); //for low level debug + int ret = -1; + + ret = mbedtls_ssl_read(&ssl_client->ssl_ctx, data, length); + + //log_v( "%d bytes read", ret); //for low level debug + return ret; +} + +static bool parseHexNibble(char pb, uint8_t* res) +{ + if (pb >= '0' && pb <= '9') { + *res = (uint8_t) (pb - '0'); return true; + } else if (pb >= 'a' && pb <= 'f') { + *res = (uint8_t) (pb - 'a' + 10); return true; + } else if (pb >= 'A' && pb <= 'F') { + *res = (uint8_t) (pb - 'A' + 10); return true; + } + return false; +} + +// Compare a name from certificate and domain name, return true if they match +static bool matchName(const std::string& name, const std::string& domainName) +{ + size_t wildcardPos = name.find('*'); + if (wildcardPos == std::string::npos) { + // Not a wildcard, expect an exact match + return name == domainName; + } + + size_t firstDotPos = name.find('.'); + if (wildcardPos > firstDotPos) { + // Wildcard is not part of leftmost component of domain name + // Do not attempt to match (rfc6125 6.4.3.1) + return false; + } + if (wildcardPos != 0 || firstDotPos != 1) { + // Matching of wildcards such as baz*.example.com and b*z.example.com + // is optional. Maybe implement this in the future? + return false; + } + size_t domainNameFirstDotPos = domainName.find('.'); + if (domainNameFirstDotPos == std::string::npos) { + return false; + } + return domainName.substr(domainNameFirstDotPos) == name.substr(firstDotPos); +} + +// Verifies certificate provided by the peer to match specified SHA256 fingerprint +bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* fp, const char* domain_name) +{ + // Convert hex string to byte array + uint8_t fingerprint_local[32]; + int len = strlen(fp); + int pos = 0; + for (size_t i = 0; i < sizeof(fingerprint_local); ++i) { + while (pos < len && ((fp[pos] == ' ') || (fp[pos] == ':'))) { + ++pos; + } + if (pos > len - 2) { + log_d("pos:%d len:%d fingerprint too short", pos, len); + return false; + } + uint8_t high, low; + if (!parseHexNibble(fp[pos], &high) || !parseHexNibble(fp[pos+1], &low)) { + log_d("pos:%d len:%d invalid hex sequence: %c%c", pos, len, fp[pos], fp[pos+1]); + return false; + } + pos += 2; + fingerprint_local[i] = low | (high << 4); + } + + // Get certificate provided by the peer + const mbedtls_x509_crt* crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx); + + if (!crt) + { + log_d("could not fetch peer certificate"); + return false; + } + + // Calculate certificate's SHA256 fingerprint + uint8_t fingerprint_remote[32]; + mbedtls_sha256_context sha256_ctx; + mbedtls_sha256_init(&sha256_ctx); + mbedtls_sha256_starts(&sha256_ctx, false); + mbedtls_sha256_update(&sha256_ctx, crt->raw.p, crt->raw.len); + mbedtls_sha256_finish(&sha256_ctx, fingerprint_remote); + mbedtls_sha256_free(&sha256_ctx); + + // Check if fingerprints match + if (memcmp(fingerprint_local, fingerprint_remote, 32)) + { + log_d("fingerprint doesn't match"); + return false; + } + + // Additionally check if certificate has domain name if provided + if (domain_name) + return verify_ssl_dn(ssl_client, domain_name); + else + return true; +} + +// Checks if peer certificate has specified domain in CN or SANs +bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name) +{ + log_d("domain name: '%s'", (domain_name)?domain_name:"(null)"); + std::string domain_name_str(domain_name); + std::transform(domain_name_str.begin(), domain_name_str.end(), domain_name_str.begin(), ::tolower); + + // Get certificate provided by the peer + const mbedtls_x509_crt* crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx); + + // Check for domain name in SANs + const mbedtls_x509_sequence* san = &crt->subject_alt_names; + while (san != nullptr) + { + std::string san_str((const char*)san->buf.p, san->buf.len); + std::transform(san_str.begin(), san_str.end(), san_str.begin(), ::tolower); + + if (matchName(san_str, domain_name_str)) + return true; + + log_d("SAN '%s': no match", san_str.c_str()); + + // Fetch next SAN + san = san->next; + } + + // Check for domain name in CN + const mbedtls_asn1_named_data* common_name = &crt->subject; + while (common_name != nullptr) + { + // While iterating through DN objects, check for CN object + if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &common_name->oid)) + { + std::string common_name_str((const char*)common_name->val.p, common_name->val.len); + + if (matchName(common_name_str, domain_name_str)) + return true; + + log_d("CN '%s': not match", common_name_str.c_str()); + } + + // Fetch next DN object + common_name = common_name->next; + } + + return false; +} \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_ssl_client.h b/src/src/Helpers/ESPEasy_ssl_client.h new file mode 100644 index 0000000000..46bc0d649f --- /dev/null +++ b/src/src/Helpers/ESPEasy_ssl_client.h @@ -0,0 +1,53 @@ +/* Provide SSL/TLS functions to ESP32 with Arduino IDE + * by Evandro Copercini - 2017 - Apache 2.0 License + */ + +#ifndef ESPEASY_ARD_SSL_H +#define ESPEASY_ARD_SSL_H +#include +#include +#include +#include +#include +#include +#include + +typedef struct ESPEasy_sslclient_context { + + ESPEasy_sslclient_context(); + + ~ESPEasy_sslclient_context(); + + void free_ca_cert(); + void free_client_cert(); + void free_client_key(); + + int socket = 0; + mbedtls_ssl_context ssl_ctx; + mbedtls_ssl_config ssl_conf; + + mbedtls_ctr_drbg_context drbg_ctx; + mbedtls_entropy_context entropy_ctx; + + mbedtls_x509_crt ca_cert; + mbedtls_x509_crt client_cert; + mbedtls_pk_context client_key; + + bool ca_cert_init = false; + bool client_cert_init = false; + bool client_key_init = false; + + unsigned long handshake_timeout = 120000; +} ESPEasy_sslclient_context; + + +void ssl_init(ESPEasy_sslclient_context *ssl_client); +int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure); +void stop_ssl_socket(ESPEasy_sslclient_context *ssl_client, const char *rootCABuff, const char *cli_cert, const char *cli_key); +int data_to_read(ESPEasy_sslclient_context *ssl_client); +int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, uint16_t len); +int get_ssl_receive(ESPEasy_sslclient_context *ssl_client, uint8_t *data, int length); +bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* fp, const char* domain_name); +bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name); + +#endif \ No newline at end of file From eb8d956b46a884e07d645bfeb383de7a4da1bf0e Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 11:14:04 +0100 Subject: [PATCH 025/100] [MQTT TLS] Fix memory leak... finally --- src/src/ESPEasyCore/Controller.cpp | 32 +++++++++++++++----------- src/src/Helpers/ESPEasy_Storage.cpp | 21 ----------------- src/src/Helpers/ESPEasy_ssl_client.cpp | 22 +++--------------- src/src/Helpers/ESPEasy_ssl_client.h | 6 +---- 4 files changed, 22 insertions(+), 59 deletions(-) diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index fafa1f4717..56c897ffa5 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -186,12 +186,10 @@ bool MQTTConnect(controllerIndex_t controller_idx) if (MQTTclient.connected()) { MQTTclient.disconnect(); # ifdef USE_MQTT_TLS - /* if (mqtt_tls != nullptr) { delete mqtt_tls; mqtt_tls = nullptr; } - */ #endif } @@ -206,7 +204,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) mqtt_tls_last_errorstr = EMPTY_STRING; mqtt_tls_last_error = 0; const TLS_types TLS_type = ControllerSettings.TLStype(); - if (TLS_type != TLS_types::NoTLS) { + if (TLS_type != TLS_types::NoTLS && nullptr == mqtt_tls) { #ifdef ESP32 mqtt_tls = new ESPEasy_WiFiClientSecure; #endif @@ -217,6 +215,8 @@ bool MQTTConnect(controllerIndex_t controller_idx) if (mqtt_tls == nullptr) { addLog(LOG_LEVEL_ERROR, F("MQTT : Could not create TLS client, out of memory")); return false; + } else { + mqtt_rootCA.clear(); } } switch(TLS_type) { @@ -233,9 +233,12 @@ bool MQTTConnect(controllerIndex_t controller_idx) } case TLS_types::TLS_CA_CERT: { -// mqtt_rootCA.clear(); - if (mqtt_rootCA.isEmpty()) + mqtt_rootCA.clear(); + bool certLoaded = false; + if (mqtt_rootCA.isEmpty()) { LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_rootCA); + certLoaded = true; + } { static int previousFree = FreeMem(); @@ -253,15 +256,16 @@ bool MQTTConnect(controllerIndex_t controller_idx) previousFree = freemem; } - - if (mqtt_rootCA.length() > 0) { - #ifdef ESP32 - mqtt_tls->setCACert(mqtt_rootCA.c_str()); - #endif - #ifdef ESP8266 - mqtt_X509List.append(mqtt_rootCA.c_str()); - mqtt_tls->setTrustAnchors(&mqtt_X509List); - #endif + if (certLoaded) { + if (mqtt_rootCA.length() > 0) { + #ifdef ESP32 + mqtt_tls->setCACert(mqtt_rootCA.c_str()); + #endif + #ifdef ESP8266 + mqtt_X509List.append(mqtt_rootCA.c_str()); + mqtt_tls->setTrustAnchors(&mqtt_X509List); + #endif + } } break; } diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index b461bd914c..c89b6ea45e 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -1115,10 +1115,6 @@ String LoadCertificate(const String& fname, String& certificate) } f.close(); - String analyse = F("Cleanup: Before: "); - analyse += certificate.length(); - analyse += F(" After: "); - if (!cleanupCertificate(certificate, changed)) { certificate.clear(); #ifndef BUILD_NO_DEBUG @@ -1129,23 +1125,6 @@ String LoadCertificate(const String& fname, String& certificate) } else if (changed) { //return SaveCertificate(fname, certificate); } -// addLog(LOG_LEVEL_INFO, F("After")); -// addLog(LOG_LEVEL_INFO, certificate); - static int previousFree = FreeMem(); - const int freemem = FreeMem(); - - analyse += certificate.length(); - analyse += changed ? F(" changed") : F(" same"); - analyse += F(" free memory: "); - analyse += freemem; - analyse += F(" largest free block: "); - analyse += getMaxFreeBlock(); - - analyse += F(" Difference: "); - analyse += previousFree - freemem; - - addLog(LOG_LEVEL_INFO, analyse); - previousFree = freemem; } return EMPTY_STRING; diff --git a/src/src/Helpers/ESPEasy_ssl_client.cpp b/src/src/Helpers/ESPEasy_ssl_client.cpp index 4ff9996b3b..baa7b67b1c 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.cpp +++ b/src/src/Helpers/ESPEasy_ssl_client.cpp @@ -65,30 +65,17 @@ ESPEasy_sslclient_context::~ESPEasy_sslclient_context() void ESPEasy_sslclient_context::free_ca_cert() { -// if (ca_cert.p != nullptr) { - if (ca_cert_init) { - ca_cert_init = false; - } - mbedtls_x509_crt_free(&ca_cert); -// } + mbedtls_x509_crt_free(&ca_cert); } void ESPEasy_sslclient_context::free_client_cert() { - if (client_cert_init) { - client_cert_init = false; - } - mbedtls_x509_crt_free(&client_cert); -// } + mbedtls_x509_crt_free(&client_cert); } void ESPEasy_sslclient_context::free_client_key() { - if (client_key_init) { - client_key_init = false; - } - mbedtls_pk_free(&client_key); -// } + mbedtls_pk_free(&client_key); } @@ -182,7 +169,6 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui } else if (rootCABuff != NULL) { log_v("Loading CA cert"); mbedtls_x509_crt_init(&ssl_client->ca_cert); - ssl_client->ca_cert_init = true; mbedtls_ssl_conf_authmode(&ssl_client->ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED); ret = mbedtls_x509_crt_parse(&ssl_client->ca_cert, (const unsigned char *)rootCABuff, strlen(rootCABuff) + 1); mbedtls_ssl_conf_ca_chain(&ssl_client->ssl_conf, &ssl_client->ca_cert, NULL); @@ -234,7 +220,6 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui log_v("Loading CRT cert"); ret = mbedtls_x509_crt_parse(&ssl_client->client_cert, (const unsigned char *)cli_cert, strlen(cli_cert) + 1); - ssl_client->client_cert_init = true; if (ret < 0) { // free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash. ssl_client->free_client_cert(); @@ -243,7 +228,6 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui log_v("Loading private key"); ret = mbedtls_pk_parse_key(&ssl_client->client_key, (const unsigned char *)cli_key, strlen(cli_key) + 1, NULL, 0); - ssl_client->client_key_init = true; if (ret != 0) { return handle_error(ret); diff --git a/src/src/Helpers/ESPEasy_ssl_client.h b/src/src/Helpers/ESPEasy_ssl_client.h index 46bc0d649f..9704b3ff80 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.h +++ b/src/src/Helpers/ESPEasy_ssl_client.h @@ -22,7 +22,7 @@ typedef struct ESPEasy_sslclient_context { void free_client_cert(); void free_client_key(); - int socket = 0; + int socket = -1; mbedtls_ssl_context ssl_ctx; mbedtls_ssl_config ssl_conf; @@ -33,10 +33,6 @@ typedef struct ESPEasy_sslclient_context { mbedtls_x509_crt client_cert; mbedtls_pk_context client_key; - bool ca_cert_init = false; - bool client_cert_init = false; - bool client_key_init = false; - unsigned long handshake_timeout = 120000; } ESPEasy_sslclient_context; From 57a82b1df2b4fb3aac49544ba48c061bbb587b3a Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 12:06:20 +0100 Subject: [PATCH 026/100] [MQTT TLS] Clear CA certificate when starting a new connection Otherwise you may not always use the latest CA root certificate stored on the file system --- src/src/ESPEasyCore/Controller.cpp | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 56c897ffa5..38cdc20f3f 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -190,6 +190,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) delete mqtt_tls; mqtt_tls = nullptr; } + mqtt_rootCA.clear(); #endif } @@ -211,12 +212,11 @@ bool MQTTConnect(controllerIndex_t controller_idx) #ifdef ESP8266 mqtt_tls = new BearSSL::WiFiClientSecure; #endif + mqtt_rootCA.clear(); if (mqtt_tls == nullptr) { addLog(LOG_LEVEL_ERROR, F("MQTT : Could not create TLS client, out of memory")); return false; - } else { - mqtt_rootCA.clear(); } } switch(TLS_type) { @@ -234,12 +234,6 @@ bool MQTTConnect(controllerIndex_t controller_idx) case TLS_types::TLS_CA_CERT: { mqtt_rootCA.clear(); - bool certLoaded = false; - if (mqtt_rootCA.isEmpty()) { - LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_rootCA); - certLoaded = true; - } - { static int previousFree = FreeMem(); const int freemem = FreeMem(); @@ -256,16 +250,16 @@ bool MQTTConnect(controllerIndex_t controller_idx) previousFree = freemem; } - if (certLoaded) { - if (mqtt_rootCA.length() > 0) { - #ifdef ESP32 - mqtt_tls->setCACert(mqtt_rootCA.c_str()); - #endif - #ifdef ESP8266 - mqtt_X509List.append(mqtt_rootCA.c_str()); - mqtt_tls->setTrustAnchors(&mqtt_X509List); - #endif - } + if (mqtt_rootCA.isEmpty()) { + LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_rootCA); + + #ifdef ESP32 + mqtt_tls->setCACert(mqtt_rootCA.c_str()); + #endif + #ifdef ESP8266 + mqtt_X509List.append(mqtt_rootCA.c_str()); + mqtt_tls->setTrustAnchors(&mqtt_X509List); + #endif } break; } From baf705df3e5ab2009b731c73096bca1fc73be676 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 12:18:06 +0100 Subject: [PATCH 027/100] [Cleanup] Fix lots of missing delete calls to free memory In a lot of places an object was allocated on the heap, but not always it was deleted thus leading to memory leaks. --- src/_P095_ILI9341.ino | 13 +++++++++++++ src/_P096_eInk.ino | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/src/_P095_ILI9341.ino b/src/_P095_ILI9341.ino index 9fb383959a..a8cb5fc05e 100644 --- a/src/_P095_ILI9341.ino +++ b/src/_P095_ILI9341.ino @@ -246,6 +246,10 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) TFT_Settings.address_tft_dc = PIN(1); TFT_Settings.address_tft_rst = PIN(2); TFT_Settings.rotation = PCONFIG(1); + if (tft != nullptr) { + delete tft; + tft = nullptr; + } initPluginTaskData(event->TaskIndex, new (std::nothrow) P095_data_struct( @@ -264,6 +268,15 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) break; } + case PLUGIN_EXIT: + { + if (tft != nullptr) { + delete tft; + tft = nullptr; + } + break; + } + case PLUGIN_WRITE: { String tmpString = String(string); diff --git a/src/_P096_eInk.ino b/src/_P096_eInk.ino index 083a18d493..74adafe53d 100644 --- a/src/_P096_eInk.ino +++ b/src/_P096_eInk.ino @@ -305,6 +305,15 @@ boolean Plugin_096(uint8_t function, struct EventStruct *event, String& string) break; } + case PLUGIN_EXIT: + { + if (eInkScreen != nullptr) { + delete eInkScreen; + eInkScreen = nullptr; + } + break; + } + case PLUGIN_WRITE: { #ifndef BUILD_NO_DEBUG From df010cdae0727e30bfaedd7d1cd7f8750cfd6922 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 4 Nov 2021 23:58:22 +0100 Subject: [PATCH 028/100] [Webserver] Fix serving CSS I made an error in previous commit for this PR --- src/src/Helpers/ESPEasy_Storage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index c89b6ea45e..4df93d332d 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -125,7 +125,7 @@ bool fileExists(const String& fname) { const String patched_fname = patch_fname(fname); auto search = Cache.fileExistsMap.find(patched_fname); if (search != Cache.fileExistsMap.end()) { - return search->second >= 0; + return search->second; } int size = -1; if (ESPEASY_FS.exists(patched_fname)) { From 40d17e04037701aaf21bf21b52e24b79fceca105 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 6 Nov 2021 17:21:22 +0100 Subject: [PATCH 029/100] [TLS] Add ALPN protocol --- src/src/Helpers/ESPEasy_WiFiClientSecure.cpp | 11 ++++- src/src/Helpers/ESPEasy_WiFiClientSecure.h | 4 ++ src/src/Helpers/ESPEasy_ssl_client.cpp | 48 +++++++++++++------- src/src/Helpers/ESPEasy_ssl_client.h | 3 +- 4 files changed, 47 insertions(+), 19 deletions(-) diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp index f84c5aeb40..c08a7d22bd 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp @@ -41,6 +41,7 @@ ESPEasy_WiFiClientSecure::ESPEasy_WiFiClientSecure() _pskIdent = NULL; _psKey = NULL; next = NULL; + _alpn_protos = NULL; } @@ -64,6 +65,7 @@ ESPEasy_WiFiClientSecure::ESPEasy_WiFiClientSecure(int sock) _pskIdent = NULL; _psKey = NULL; next = NULL; + _alpn_protos = NULL; } ESPEasy_WiFiClientSecure::~ESPEasy_WiFiClientSecure() @@ -125,7 +127,7 @@ int ESPEasy_WiFiClientSecure::connect(const char *host, uint16_t port, const cha if(_timeout > 0){ sslclient->handshake_timeout = _timeout; } - int ret = start_ssl_client(sslclient, host, port, _timeout, CA_cert, cert, private_key, NULL, NULL, _use_insecure); + int ret = start_ssl_client(sslclient, host, port, _timeout, CA_cert, cert, private_key, NULL, NULL, _use_insecure, _alpn_protos); _lastError = ret; if (ret < 0) { log_e("start_ssl_client: %d", ret); @@ -145,7 +147,7 @@ int ESPEasy_WiFiClientSecure::connect(const char *host, uint16_t port, const cha if(_timeout > 0){ sslclient->handshake_timeout = _timeout; } - int ret = start_ssl_client(sslclient, host, port, _timeout, NULL, NULL, NULL, pskIdent, psKey, _use_insecure); + int ret = start_ssl_client(sslclient, host, port, _timeout, NULL, NULL, NULL, pskIdent, psKey, _use_insecure, _alpn_protos); _lastError = ret; if (ret < 0) { log_e("start_ssl_client: %d", ret); @@ -338,4 +340,9 @@ int ESPEasy_WiFiClientSecure::lastError(char *buf, const size_t size) void ESPEasy_WiFiClientSecure::setHandshakeTimeout(unsigned long handshake_timeout) { sslclient->handshake_timeout = handshake_timeout * 1000; +} + +void ESPEasy_WiFiClientSecure::setAlpnProtocols(const char **alpn_protos) +{ + _alpn_protos = alpn_protos; } \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.h b/src/src/Helpers/ESPEasy_WiFiClientSecure.h index 08e129446b..e219b37c40 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.h +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.h @@ -42,6 +42,7 @@ class ESPEasy_WiFiClientSecure : public WiFiClient const char *_private_key; const char *_pskIdent; // identity for PSK cipher suites const char *_psKey; // key in hex for PSK cipher suites + const char **_alpn_protos; public: ESPEasy_WiFiClientSecure *next; @@ -76,6 +77,9 @@ class ESPEasy_WiFiClientSecure : public WiFiClient bool loadPrivateKey(Stream& stream, size_t size); bool verify(const char* fingerprint, const char* domain_name); void setHandshakeTimeout(unsigned long handshake_timeout); + void setAlpnProtocols(const char **alpn_protos); + const mbedtls_x509_crt* getPeerCertificate() { return mbedtls_ssl_get_peer_cert(&sslclient->ssl_ctx); }; + bool getFingerprintSHA256(uint8_t sha256_result[32]) { return get_peer_fingerprint(sslclient, sha256_result); }; int setTimeout(uint32_t seconds){ return 0; } diff --git a/src/src/Helpers/ESPEasy_ssl_client.cpp b/src/src/Helpers/ESPEasy_ssl_client.cpp index baa7b67b1c..ebeabe99c5 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.cpp +++ b/src/src/Helpers/ESPEasy_ssl_client.cpp @@ -91,7 +91,7 @@ void ssl_init(ESPEasy_sslclient_context *ssl_client) } -int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure) +int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos) { char buf[512]; int ret, flags; @@ -160,6 +160,13 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui return handle_error(ret); } + if (alpn_protos != NULL) { + log_v("Setting ALPN protocols"); + if ((ret = mbedtls_ssl_conf_alpn_protocols(&ssl_client->ssl_conf, alpn_protos) ) != 0) { + return handle_error(ret); + } + } + // MBEDTLS_SSL_VERIFY_REQUIRED if a CA certificate is defined on Arduino IDE and // MBEDTLS_SSL_VERIFY_NONE if not. @@ -433,23 +440,10 @@ bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* f fingerprint_local[i] = low | (high << 4); } - // Get certificate provided by the peer - const mbedtls_x509_crt* crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx); - - if (!crt) - { - log_d("could not fetch peer certificate"); - return false; - } - // Calculate certificate's SHA256 fingerprint uint8_t fingerprint_remote[32]; - mbedtls_sha256_context sha256_ctx; - mbedtls_sha256_init(&sha256_ctx); - mbedtls_sha256_starts(&sha256_ctx, false); - mbedtls_sha256_update(&sha256_ctx, crt->raw.p, crt->raw.len); - mbedtls_sha256_finish(&sha256_ctx, fingerprint_remote); - mbedtls_sha256_free(&sha256_ctx); + if(!get_peer_fingerprint(ssl_client, fingerprint_remote)) + return false; // Check if fingerprints match if (memcmp(fingerprint_local, fingerprint_remote, 32)) @@ -465,6 +459,28 @@ bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* f return true; } +bool get_peer_fingerprint(ESPEasy_sslclient_context *ssl_client, uint8_t sha256[32]) +{ + if (!ssl_client) { + log_d("Invalid ssl_client pointer"); + return false; + }; + + const mbedtls_x509_crt* crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx); + if (!crt) { + log_d("Failed to get peer cert."); + return false; + }; + + mbedtls_sha256_context sha256_ctx; + mbedtls_sha256_init(&sha256_ctx); + mbedtls_sha256_starts(&sha256_ctx, false); + mbedtls_sha256_update(&sha256_ctx, crt->raw.p, crt->raw.len); + mbedtls_sha256_finish(&sha256_ctx, sha256); + + return true; +} + // Checks if peer certificate has specified domain in CN or SANs bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name) { diff --git a/src/src/Helpers/ESPEasy_ssl_client.h b/src/src/Helpers/ESPEasy_ssl_client.h index 9704b3ff80..de17f30709 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.h +++ b/src/src/Helpers/ESPEasy_ssl_client.h @@ -38,12 +38,13 @@ typedef struct ESPEasy_sslclient_context { void ssl_init(ESPEasy_sslclient_context *ssl_client); -int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure); +int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos); void stop_ssl_socket(ESPEasy_sslclient_context *ssl_client, const char *rootCABuff, const char *cli_cert, const char *cli_key); int data_to_read(ESPEasy_sslclient_context *ssl_client); int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, uint16_t len); int get_ssl_receive(ESPEasy_sslclient_context *ssl_client, uint8_t *data, int length); bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* fp, const char* domain_name); bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name); +bool get_peer_fingerprint(ESPEasy_sslclient_context *ssl_client, uint8_t sha256[32]); #endif \ No newline at end of file From 97a4594fa93895ed3396e28baa6f3006152ff026 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 6 Nov 2021 18:01:27 +0100 Subject: [PATCH 030/100] [TLS] Add fix for WiFiClientSecure connection timeout --- src/src/Helpers/ESPEasy_WiFiClientSecure.h | 2 +- src/src/Helpers/ESPEasy_ssl_client.cpp | 100 ++++++++++++++------- src/src/Helpers/ESPEasy_ssl_client.h | 3 +- 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.h b/src/src/Helpers/ESPEasy_WiFiClientSecure.h index e219b37c40..c228befd7a 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.h +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.h @@ -57,7 +57,7 @@ class ESPEasy_WiFiClientSecure : public WiFiClient int connect(const char *host, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key); int connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psKey); int connect(const char *host, uint16_t port, const char *pskIdent, const char *psKey); - int peek(); + int peek(); size_t write(uint8_t data); size_t write(const uint8_t *buf, size_t size); int available(); diff --git a/src/src/Helpers/ESPEasy_ssl_client.cpp b/src/src/Helpers/ESPEasy_ssl_client.cpp index ebeabe99c5..a867641a8c 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.cpp +++ b/src/src/Helpers/ESPEasy_ssl_client.cpp @@ -21,12 +21,12 @@ #include #ifndef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED -# error "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher" -#endif +# warning "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher" +#else const char *ESPEasy_pers = "esp32-tls"; -static int _handle_error(int err, const char * file, int line) +static int _handle_error(int err, const char * function, int line) { if(err == -30848){ return err; @@ -34,15 +34,16 @@ static int _handle_error(int err, const char * file, int line) #ifdef MBEDTLS_ERROR_C char error_buf[100]; mbedtls_strerror(err, error_buf, 100); - log_e("[%s():%d]: (%d) %s", file, line, err, error_buf); + log_e("[%s():%d]: (%d) %s", function, line, err, error_buf); #else - log_e("[%s():%d]: code %d", file, line, err); + log_e("[%s():%d]: code %d", function, line, err); #endif return err; } #define handle_error(e) _handle_error(e, __FUNCTION__, __LINE__) + ESPEasy_sslclient_context::ESPEasy_sslclient_context() { memset(&ssl_ctx, 0, sizeof(ssl_ctx)); @@ -116,30 +117,67 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui return -1; } + fcntl( ssl_client->socket, F_SETFL, fcntl( ssl_client->socket, F_GETFL, 0 ) | O_NONBLOCK ); struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = srv; serv_addr.sin_port = htons(port); - if (lwip_connect(ssl_client->socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == 0) { - if(timeout <= 0){ - timeout = 30000; // Milli seconds. - } - timeval so_timeout = { .tv_sec = timeout / 1000, .tv_usec = (timeout % 1000) * 1000 }; + if(timeout <= 0){ + timeout = 30000; // Milli seconds. + } -#define ROE(x,msg) { if (((x)<0)) { log_e("LWIP Socket config of " msg " failed."); return -1; }} - ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_RCVTIMEO, &so_timeout, sizeof(so_timeout)),"SO_RCVTIMEO"); - ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_SNDTIMEO, &so_timeout, sizeof(so_timeout)),"SO_SNDTIMEO"); + fd_set fdset; + struct timeval tv; + FD_ZERO(&fdset); + FD_SET(ssl_client->socket, &fdset); + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; - ROE(lwip_setsockopt(ssl_client->socket, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)),"TCP_NODELAY"); - ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)),"SO_KEEPALIVE"); - } else { - log_e("Connect to Server failed!"); + int res = lwip_connect(ssl_client->socket, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); + if (res < 0 && errno != EINPROGRESS) { + log_e("connect on fd %d, errno: %d, \"%s\"", ssl_client->socket, errno, strerror(errno)); + close(ssl_client->socket); return -1; } - fcntl( ssl_client->socket, F_SETFL, fcntl( ssl_client->socket, F_GETFL, 0 ) | O_NONBLOCK ); + res = select(ssl_client->socket + 1, nullptr, &fdset, nullptr, timeout<0 ? nullptr : &tv); + if (res < 0) { + log_e("select on fd %d, errno: %d, \"%s\"", ssl_client->socket, errno, strerror(errno)); + close(ssl_client->socket); + return -1; + } else if (res == 0) { + log_i("select returned due to timeout %d ms for fd %d", timeout, ssl_client->socket); + close(ssl_client->socket); + return -1; + } else { + int sockerr; + socklen_t len = (socklen_t)sizeof(int); + res = getsockopt(ssl_client->socket, SOL_SOCKET, SO_ERROR, &sockerr, &len); + + if (res < 0) { + log_e("getsockopt on fd %d, errno: %d, \"%s\"", ssl_client->socket, errno, strerror(errno)); + close(ssl_client->socket); + return -1; + } + + if (sockerr != 0) { + log_e("socket error on fd %d, errno: %d, \"%s\"", ssl_client->socket, sockerr, strerror(sockerr)); + close(ssl_client->socket); + return -1; + } + } + + +#define ROE(x,msg) { if (((x)<0)) { log_e("LWIP Socket config of " msg " failed."); return -1; }} + ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),"SO_RCVTIMEO"); + ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),"SO_SNDTIMEO"); + + ROE(lwip_setsockopt(ssl_client->socket, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)),"TCP_NODELAY"); + ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)),"SO_KEEPALIVE"); + + log_v("Seeding the random number generator"); mbedtls_entropy_free(&ssl_client->entropy_ctx); @@ -166,7 +204,7 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui return handle_error(ret); } } - + // MBEDTLS_SSL_VERIFY_REQUIRED if a CA certificate is defined on Arduino IDE and // MBEDTLS_SSL_VERIFY_NONE if not. @@ -223,12 +261,11 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui mbedtls_x509_crt_init(&ssl_client->client_cert); mbedtls_pk_init(&ssl_client->client_key); - log_v("Loading CRT cert"); ret = mbedtls_x509_crt_parse(&ssl_client->client_cert, (const unsigned char *)cli_cert, strlen(cli_cert) + 1); if (ret < 0) { - // free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash. + // free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash. ssl_client->free_client_cert(); return handle_error(ret); } @@ -275,9 +312,9 @@ int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, ui ssl_client->free_ca_cert(); ssl_client->free_client_cert(); // ++++++++++ END ++++++++++ - return -1; + return -1; } - vTaskDelay(2);//2 ticks + vTaskDelay(2);//2 ticks } @@ -351,16 +388,18 @@ int data_to_read(ESPEasy_sslclient_context *ssl_client) return res; } -int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, uint16_t len) +int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, size_t len) { log_v("Writing HTTP request with %d bytes...", len); //for low level debug int ret = -1; - if ((ret = mbedtls_ssl_write(&ssl_client->ssl_ctx, data, len)) <= 0){ - log_v("Handling error %d", ret); //for low level debug - return handle_error(ret); - } else{ - log_v("Returning with %d bytes written", ret); //for low level debug + while ((ret = mbedtls_ssl_write(&ssl_client->ssl_ctx, data, len)) <= 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) { + log_v("Handling error %d", ret); //for low level debug + return handle_error(ret); + } + //wait for space to become available + vTaskDelay(2); } return ret; @@ -527,4 +566,5 @@ bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_nam } return false; -} \ No newline at end of file +} +#endif diff --git a/src/src/Helpers/ESPEasy_ssl_client.h b/src/src/Helpers/ESPEasy_ssl_client.h index de17f30709..86560b0faa 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.h +++ b/src/src/Helpers/ESPEasy_ssl_client.h @@ -41,10 +41,9 @@ void ssl_init(ESPEasy_sslclient_context *ssl_client); int start_ssl_client(ESPEasy_sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos); void stop_ssl_socket(ESPEasy_sslclient_context *ssl_client, const char *rootCABuff, const char *cli_cert, const char *cli_key); int data_to_read(ESPEasy_sslclient_context *ssl_client); -int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, uint16_t len); +int send_ssl_data(ESPEasy_sslclient_context *ssl_client, const uint8_t *data, size_t len); int get_ssl_receive(ESPEasy_sslclient_context *ssl_client, uint8_t *data, int length); bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* fp, const char* domain_name); bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name); bool get_peer_fingerprint(ESPEasy_sslclient_context *ssl_client, uint8_t sha256[32]); - #endif \ No newline at end of file From 89e9f23e2ce0000266f623a92b1efc4e5ac08309 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 6 Nov 2021 21:52:29 +0100 Subject: [PATCH 031/100] [MQTT TLS]Display connection info on controller page --- src/src/ESPEasyCore/Controller.cpp | 15 ++++++++- src/src/Helpers/ESPEasy_WiFiClientSecure.cpp | 22 ++++++++++++- src/src/Helpers/ESPEasy_WiFiClientSecure.h | 4 +++ src/src/Helpers/ESPEasy_ssl_client.cpp | 3 ++ src/src/Helpers/ESPEasy_ssl_client.h | 3 +- src/src/WebServer/ControllerPage.cpp | 33 ++++++++++++++++++++ 6 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 38cdc20f3f..84772d6dd3 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -202,7 +202,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) uint16_t mqttPort = ControllerSettings.Port; #ifdef USE_MQTT_TLS - mqtt_tls_last_errorstr = EMPTY_STRING; + mqtt_tls_last_errorstr.clear(); mqtt_tls_last_error = 0; const TLS_types TLS_type = ControllerSettings.TLStype(); if (TLS_type != TLS_types::NoTLS && nullptr == mqtt_tls) { @@ -277,6 +277,8 @@ bool MQTTConnect(controllerIndex_t controller_idx) } } if (TLS_type != TLS_types::NoTLS) { + // Certificate expiry not enabled in Mbed TLS. +// mqtt_tls->setX509Time(node_time.getUnixTime()); mqtt_tls->setTimeout(ControllerSettings.ClientTimeout); #ifdef ESP8266 mqtt_tls->setBufferSizes(1024,1024); @@ -380,6 +382,17 @@ bool MQTTConnect(controllerIndex_t controller_idx) log += clientid; addLog(LOG_LEVEL_INFO, log); + + #ifdef USE_MQTT_TLS + #ifdef ESP32 + { + log = F("MQTT : Peer certificate info: "); + log += mqtt_tls->getPeerCertificateInfo(); + addLog(LOG_LEVEL_INFO, log); + log.clear(); + } + #endif + #endif String subscribeTo = ControllerSettings.Subscribe; parseSystemVariables(subscribeTo, false); diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp index c08a7d22bd..b9ee2e5e43 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp @@ -17,6 +17,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef ESP32 #include #include #include @@ -345,4 +346,23 @@ void ESPEasy_WiFiClientSecure::setHandshakeTimeout(unsigned long handshake_timeo void ESPEasy_WiFiClientSecure::setAlpnProtocols(const char **alpn_protos) { _alpn_protos = alpn_protos; -} \ No newline at end of file +} + +String ESPEasy_WiFiClientSecure::getPeerCertificateInfo() +{ + const mbedtls_x509_crt* peer = getPeerCertificate(); + String res; + if (peer != nullptr) { + char buf[1024] = {0}; + int l = mbedtls_x509_crt_info (buf, sizeof(buf), "", peer); + if (l > 0) { + if (res.reserve(l)) { + for (int i = 0; i < l; ++i) { + res += buf[i]; + } + } + } + } + return res; +} +#endif \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.h b/src/src/Helpers/ESPEasy_WiFiClientSecure.h index c228befd7a..0ddaffc20f 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.h +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.h @@ -21,6 +21,8 @@ for memory leaks not yet present in the "older" core versions. */ +#ifdef ESP32 + #ifndef ESPEASY_WiFiClientSecure_h #define ESPEASY_WiFiClientSecure_h #include @@ -78,6 +80,7 @@ class ESPEasy_WiFiClientSecure : public WiFiClient bool verify(const char* fingerprint, const char* domain_name); void setHandshakeTimeout(unsigned long handshake_timeout); void setAlpnProtocols(const char **alpn_protos); + String getPeerCertificateInfo(); const mbedtls_x509_crt* getPeerCertificate() { return mbedtls_ssl_get_peer_cert(&sslclient->ssl_ctx); }; bool getFingerprintSHA256(uint8_t sha256_result[32]) { return get_peer_fingerprint(sslclient, sha256_result); }; @@ -114,4 +117,5 @@ class ESPEasy_WiFiClientSecure : public WiFiClient using Print::write; }; +#endif #endif \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_ssl_client.cpp b/src/src/Helpers/ESPEasy_ssl_client.cpp index a867641a8c..0cb0394e6d 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.cpp +++ b/src/src/Helpers/ESPEasy_ssl_client.cpp @@ -8,6 +8,8 @@ * Additions Copyright (C) 2017 Evandro Luis Copercini, Apache 2.0 License. */ +#ifdef ESP32 + #include #include #include @@ -568,3 +570,4 @@ bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_nam return false; } #endif +#endif \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_ssl_client.h b/src/src/Helpers/ESPEasy_ssl_client.h index 86560b0faa..5c612c3ae7 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.h +++ b/src/src/Helpers/ESPEasy_ssl_client.h @@ -1,7 +1,7 @@ /* Provide SSL/TLS functions to ESP32 with Arduino IDE * by Evandro Copercini - 2017 - Apache 2.0 License */ - +#ifdef ESP32 #ifndef ESPEASY_ARD_SSL_H #define ESPEASY_ARD_SSL_H #include @@ -46,4 +46,5 @@ int get_ssl_receive(ESPEasy_sslclient_context *ssl_client, uint8_t *data, int le bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* fp, const char* domain_name); bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name); bool get_peer_fingerprint(ESPEasy_sslclient_context *ssl_client, uint8_t sha256[32]); +#endif #endif \ No newline at end of file diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 788dc385f5..f6cb91e9f6 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -17,6 +17,10 @@ #include "../Globals/Protocol.h" #include "../Globals/Settings.h" +#ifdef USES_MQTT +#include "../Globals/MQTT.h" +#endif + #include "../Helpers/_CPlugin_Helper_webform.h" #include "../Helpers/_Plugin_SensorTypeHelper.h" #include "../Helpers/ESPEasy_Storage.h" @@ -414,6 +418,35 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addHtmlError(F("Bug in CPlugin::Function::CPLUGIN_WEBFORM_LOAD, should not append to string, use addHtml() instead")); } } + { + #ifdef USES_MQTT + if (Protocol[ProtocolIndex].usesMQTT) { + addFormSubHeader(F("Connection Info")); + addRowLabel(F("MQTT Client Connected")); + addEnabled(MQTTclient_connected); + +#ifdef USE_MQTT_TLS + if (Protocol[ProtocolIndex].usesTLS) { + addRowLabel(F("Last Error")); + addHtmlInt(mqtt_tls_last_error); + addHtml(F(": ")); + addHtml(mqtt_tls_last_errorstr); + + #ifdef ESP32 + if (MQTTclient_connected) { + addRowLabel(F("Peer Certificate")); + String peerInfo = mqtt_tls->getPeerCertificateInfo(); + peerInfo.replace(F("\n"), F("
")); + addTextBox(F("peer_cert"), peerInfo, peerInfo.length(), true); + } + #endif + + } +#endif + } + #endif + } + // Separate enabled checkbox as it doesn't need to use the ControllerSettings. // So ControllerSettings object can be destructed before controller specific settings are loaded. addControllerEnabledForm(controllerindex); From 3cf92b9f6f4055caf268eaefd8cd5bb00db94698 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 6 Nov 2021 21:53:26 +0100 Subject: [PATCH 032/100] [Cleanup] Use .clear() on strings instead of assigning empty string --- src/_C015.cpp | 2 +- src/_P050_TCS34725.ino | 4 ++-- src/src/ESPEasyCore/ESPEasyRules.cpp | 4 ++-- src/src/PluginStructs/P104_data_struct.cpp | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/_C015.cpp b/src/_C015.cpp index 8ca412089f..8a47f09372 100644 --- a/src/_C015.cpp +++ b/src/_C015.cpp @@ -194,7 +194,7 @@ bool CPlugin_015(CPlugin::Function function, struct EventStruct *event, String& if (!isvalid) { // send empty string to Blynk in case of error - formattedValue = EMPTY_STRING; + formattedValue.clear(); } String valueName = ExtraTaskSettings.TaskDeviceValueNames[x]; diff --git a/src/_P050_TCS34725.ino b/src/_P050_TCS34725.ino index 302a37cdc5..26c33b831c 100644 --- a/src/_P050_TCS34725.ino +++ b/src/_P050_TCS34725.ino @@ -430,7 +430,7 @@ boolean Plugin_050(uint8_t function, struct EventStruct *event, String& string) RuleEvent += toString(static_cast(b) / t * sRGBFactor, 4); break; default: - RuleEvent = EMPTY_STRING; + RuleEvent.clear(); break; } if (!RuleEvent.isEmpty()) { @@ -466,7 +466,7 @@ boolean Plugin_050(uint8_t function, struct EventStruct *event, String& string) RuleEvent += c; break; default: - RuleEvent = EMPTY_STRING; + RuleEvent.clear(); break; } if (!RuleEvent.isEmpty()) { diff --git a/src/src/ESPEasyCore/ESPEasyRules.cpp b/src/src/ESPEasyCore/ESPEasyRules.cpp index cc246f5082..01d70b2fae 100644 --- a/src/src/ESPEasyCore/ESPEasyRules.cpp +++ b/src/src/ESPEasyCore/ESPEasyRules.cpp @@ -252,7 +252,7 @@ String rulesProcessingFile(const String& fileName, const String& event) { } // Prepare for new line - line = EMPTY_STRING; + line.clear(); line.reserve(longestLineSize); firstNonSpaceRead = false; commentFound = false; @@ -752,7 +752,7 @@ void parseCompleteNonCommentLine(String& line, const String& event, String eventTrigger; - action = EMPTY_STRING; + action.clear(); if (!codeBlock) // do not check "on" rules if a block of actions is to be // processed diff --git a/src/src/PluginStructs/P104_data_struct.cpp b/src/src/PluginStructs/P104_data_struct.cpp index 5aa1ee2f27..cc5058e240 100644 --- a/src/src/PluginStructs/P104_data_struct.cpp +++ b/src/src/PluginStructs/P104_data_struct.cpp @@ -334,7 +334,7 @@ void P104_data_struct::loadSettings() { zones.push_back(P104_zone_struct(zoneIndex + 1)); if (zones[zoneIndex].text == F("\"\"")) { // Special case - zones[zoneIndex].text = EMPTY_STRING; + zones[zoneIndex].text.clear(); } zoneIndex++; @@ -1566,7 +1566,7 @@ String P104_data_struct::enquoteString(const String& input) { * saveSettings gather the zones data from the UI and store in customsettings **************************************/ bool P104_data_struct::saveSettings() { - error = EMPTY_STRING; // Clear + error.clear(); // Clear String zbuffer; # ifdef P104_DEBUG_DEV @@ -1681,7 +1681,7 @@ bool P104_data_struct::saveSettings() { if (zbuffer.reserve(P104_SETTINGS_BUFFER_V2 + 2)) { for (auto it = zones.begin(); it != zones.end() && error.length() == 0; ++it) { - zbuffer = EMPTY_STRING; + zbuffer.clear(); // WARNING: Order of values should match the numeric order of P104_OFFSET_* values zbuffer += it->size; // 2 From 4bb3bbf8f346379a711e38a739a1b4b12406663a Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 7 Nov 2021 01:05:32 +0100 Subject: [PATCH 033/100] [TLS] Making proper datastructure init --- src/src/Helpers/ESPEasy_ssl_client.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/src/Helpers/ESPEasy_ssl_client.cpp b/src/src/Helpers/ESPEasy_ssl_client.cpp index 0cb0394e6d..bf8ff5dbd6 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.cpp +++ b/src/src/Helpers/ESPEasy_ssl_client.cpp @@ -48,13 +48,14 @@ static int _handle_error(int err, const char * function, int line) ESPEasy_sslclient_context::ESPEasy_sslclient_context() { - memset(&ssl_ctx, 0, sizeof(ssl_ctx)); - memset(&ssl_conf, 0, sizeof(ssl_conf)); - memset(&drbg_ctx, 0, sizeof(drbg_ctx)); - memset(&entropy_ctx, 0, sizeof(entropy_ctx)); - memset(&ca_cert, 0, sizeof(ca_cert)); - memset(&client_cert, 0, sizeof(client_cert)); - memset(&client_key, 0, sizeof(client_key)); + mbedtls_ssl_init(&ssl_ctx); + mbedtls_ssl_config_init(&ssl_conf); + mbedtls_ctr_drbg_init(&drbg_ctx); + + mbedtls_entropy_init(&entropy_ctx); + mbedtls_x509_crt_init(&ca_cert); + mbedtls_x509_crt_init(&client_cert); + mbedtls_pk_init(&client_key); } From 5127f006d13a42da27b7b0f53f6becd4650d3643 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 7 Nov 2021 01:06:15 +0100 Subject: [PATCH 034/100] [MQTT TLS] Improve controller setup page show peer certificate --- src/src/WebServer/ControllerPage.cpp | 39 ++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index f6cb91e9f6..0a91d32df9 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -419,9 +419,9 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex } } { - #ifdef USES_MQTT +#ifdef USES_MQTT if (Protocol[ProtocolIndex].usesMQTT) { - addFormSubHeader(F("Connection Info")); + addFormSubHeader(F("Connection Status")); addRowLabel(F("MQTT Client Connected")); addEnabled(MQTTclient_connected); @@ -433,23 +433,46 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addHtml(mqtt_tls_last_errorstr); #ifdef ESP32 - if (MQTTclient_connected) { - addRowLabel(F("Peer Certificate")); - String peerInfo = mqtt_tls->getPeerCertificateInfo(); - peerInfo.replace(F("\n"), F("
")); - addTextBox(F("peer_cert"), peerInfo, peerInfo.length(), true); + if (MQTTclient_connected && mqtt_tls != nullptr) { + addFormSubHeader(F("Peer Certificate")); + + { + addRowLabel(F("Certificate Info")); + addHtml(F("")); + } + { + uint8_t sha256_result[32] = {0}; + if (mqtt_tls->getFingerprintSHA256(sha256_result)) { + String fingerprint; + fingerprint.reserve(64); + for (size_t i = 0; i < 32; ++i) { + fingerprint += String(sha256_result[i], HEX); + } + fingerprint.toLowerCase(); + addFormTextBox(F("Certificate Fingerprint"), + F("fingerprint"), + fingerprint, + 64, + true); // ReadOnly + } + } + + } #endif } #endif } - #endif +#endif } // Separate enabled checkbox as it doesn't need to use the ControllerSettings. // So ControllerSettings object can be destructed before controller specific settings are loaded. addControllerEnabledForm(controllerindex); + } addFormSeparator(2); From 9f0e3cf636bf2bc1df1b0728f7c88b7975ffeb09 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 25 Nov 2021 01:46:05 +0100 Subject: [PATCH 035/100] [MQTT TLS] Add certificate fingerprint validation --- .../DataStructs/ControllerSettingsStruct.cpp | 3 + .../DataStructs/ControllerSettingsStruct.h | 1 + src/src/DataTypes/TLS_types.cpp | 1 + src/src/DataTypes/TLS_types.h | 1 + src/src/ESPEasyCore/Controller.cpp | 88 ++++++++++++++++++- src/src/ESPEasyCore/Controller.h | 6 ++ src/src/Globals/MQTT.cpp | 1 + src/src/Globals/MQTT.h | 2 + src/src/Helpers/ESPEasy_Storage.cpp | 22 ++--- src/src/Helpers/ESPEasy_Storage.h | 2 +- src/src/Helpers/ESPEasy_WiFiClientSecure.h | 1 + src/src/Helpers/_CPlugin_Helper_webform.cpp | 69 +++++++++++---- src/src/WebServer/ControllerPage.cpp | 19 ++-- 13 files changed, 178 insertions(+), 38 deletions(-) diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index 791013cece..ade97bf8b5 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -349,6 +349,9 @@ String ControllerSettingsStruct::getCertificateFilename() const case TLS_types::TLS_CA_CERT: certFile += F(".cacert"); break; + case TLS_types::TLS_FINGERPRINT: + certFile += F(".fp"); + break; } return certFile; diff --git a/src/src/DataStructs/ControllerSettingsStruct.h b/src/src/DataStructs/ControllerSettingsStruct.h index 1c021015b7..92592efd19 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.h +++ b/src/src/DataStructs/ControllerSettingsStruct.h @@ -65,6 +65,7 @@ struct ControllerSettingsStruct CONTROLLER_IP, CONTROLLER_PORT, CONTROLLER_MQTT_TLS_TYPE, + CONTROLLER_MQTT_TLS_STORE_FINGERPRINT, CONTROLLER_USER, CONTROLLER_PASS, CONTROLLER_MIN_SEND_INTERVAL, diff --git a/src/src/DataTypes/TLS_types.cpp b/src/src/DataTypes/TLS_types.cpp index 5eaf38b4b1..805d8bc5a3 100644 --- a/src/src/DataTypes/TLS_types.cpp +++ b/src/src/DataTypes/TLS_types.cpp @@ -7,6 +7,7 @@ const __FlashStringHelper* toString(TLS_types tls_type) case TLS_types::TLS_PSK: return F("TLS PreSharedKey"); case TLS_types::TLS_CA_CERT: return F("TLS CA Cert"); case TLS_types::TLS_insecure: return F("TLS No Checks (insecure)"); + case TLS_types::TLS_FINGERPRINT: return F("TLS Certficate Fingerprint"); } return F("No TLS"); } diff --git a/src/src/DataTypes/TLS_types.h b/src/src/DataTypes/TLS_types.h index a8aec9aa14..3cf81fd398 100644 --- a/src/src/DataTypes/TLS_types.h +++ b/src/src/DataTypes/TLS_types.h @@ -11,6 +11,7 @@ enum class TLS_types { TLS_PSK = 1, // Pre-Shared-Key TLS_CA_CERT = 2, // Validate server certificate against known CA //TLS_CA_CLI_CERT = 3, // TLS_CA_CERT + supply client certificate for authentication + TLS_FINGERPRINT = 4, // Use certificate fingerprint TLS_insecure = 0xF // Set as last option, do not check supplied certificate. Ideal for man-in-the-middle attack. }; diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 84772d6dd3..c633ff61f0 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -215,7 +215,8 @@ bool MQTTConnect(controllerIndex_t controller_idx) mqtt_rootCA.clear(); if (mqtt_tls == nullptr) { - addLog(LOG_LEVEL_ERROR, F("MQTT : Could not create TLS client, out of memory")); + mqtt_tls_last_errorstr = F("MQTT : Could not create TLS client, out of memory"); + addLog(LOG_LEVEL_ERROR, mqtt_tls_last_errorstr); return false; } } @@ -252,6 +253,12 @@ bool MQTTConnect(controllerIndex_t controller_idx) if (mqtt_rootCA.isEmpty()) { LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_rootCA); + if (mqtt_rootCA.isEmpty()) { + // Fingerprint must be of some minimal length to continue. + mqtt_tls_last_errorstr = F("MQTT : No TLS root CA"); + addLog(LOG_LEVEL_ERROR, mqtt_tls_last_errorstr); + return false; + } #ifdef ESP32 mqtt_tls->setCACert(mqtt_rootCA.c_str()); @@ -270,8 +277,24 @@ bool MQTTConnect(controllerIndex_t controller_idx) break; } */ + case TLS_types::TLS_FINGERPRINT: + { + // Fingerprint is checked when making the connection. + mqtt_rootCA.clear(); + mqtt_fingerprint.clear(); + LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_fingerprint, false); + if (mqtt_fingerprint.length() < 32) { + // Fingerprint must be of some minimal length to continue. + mqtt_tls_last_errorstr = F("MQTT : Stored TLS fingerprint too small"); + addLog(LOG_LEVEL_ERROR, mqtt_tls_last_errorstr); + return false; + } + mqtt_tls->setInsecure(); + break; + } case TLS_types::TLS_insecure: { + mqtt_rootCA.clear(); mqtt_tls->setInsecure(); break; } @@ -353,9 +376,41 @@ bool MQTTConnect(controllerIndex_t controller_idx) #endif #ifdef ESP32 mqtt_tls_last_error = mqtt_tls->lastError(buf,128); + mqtt_tls->clearLastError(); #endif mqtt_tls_last_errorstr = buf; } + if (TLS_type == TLS_types::TLS_FINGERPRINT) + { + // Check fingerprint + if (MQTTresult) { + const int newlinepos = mqtt_fingerprint.indexOf('\n'); + String fp; + String dn; + if (ControllerSettings.UseDNS) dn = ControllerSettings.getHost(); + if (newlinepos == -1) { + fp = mqtt_fingerprint; + } else { + fp = mqtt_fingerprint.substring(0, newlinepos); + const int newlinepos2 = mqtt_fingerprint.indexOf('\n', newlinepos); + if (newlinepos2 == -1) + dn = mqtt_fingerprint.substring(newlinepos + 1); + else + dn = mqtt_fingerprint.substring(newlinepos + 1, newlinepos2); + dn.trim(); + + } + if (!mqtt_tls->verify( + fp.c_str(), + dn.isEmpty() ? nullptr : dn.c_str())) + { + mqtt_tls_last_errorstr += F("TLS Fingerprint does not match"); + addLog(LOG_LEVEL_INFO, mqtt_fingerprint); + MQTTresult = false; + } + } + } + #endif @@ -713,6 +768,37 @@ void MQTTStatus(struct EventStruct *event, const String& status) } } + +#ifdef USE_MQTT_TLS +bool GetTLSfingerprint(String& fp) +{ + #ifdef ESP32 + if (MQTTclient_connected && mqtt_tls != nullptr) { + uint8_t sha256_result[32] = {0}; + if (mqtt_tls->getFingerprintSHA256(sha256_result)) { + fp.reserve(64); + for (size_t i = 0; i < 32; ++i) { + const String tmp(sha256_result[i], HEX); + switch (tmp.length()) { + case 0: + fp += '0'; + // fall through + case 1: + fp += '0'; + break; + } + fp += tmp; + } + fp.toLowerCase(); + return true; + } + } + #endif + return false; +} + +#endif + #endif // USES_MQTT diff --git a/src/src/ESPEasyCore/Controller.h b/src/src/ESPEasyCore/Controller.h index 0a69b48a4f..e14ac48748 100644 --- a/src/src/ESPEasyCore/Controller.h +++ b/src/src/ESPEasyCore/Controller.h @@ -69,6 +69,12 @@ bool MQTTpublish(controllerIndex_t controller_idx, taskIndex_t taskIndex, Strin * Send status info back to channel where request came from \*********************************************************************************************/ void MQTTStatus(struct EventStruct *event, const String& status); + +#ifdef USE_MQTT_TLS +bool GetTLSfingerprint(String& fp); + +#endif + #endif //USES_MQTT diff --git a/src/src/Globals/MQTT.cpp b/src/src/Globals/MQTT.cpp index 3434c4b42b..89cb8e9470 100644 --- a/src/src/Globals/MQTT.cpp +++ b/src/src/Globals/MQTT.cpp @@ -19,6 +19,7 @@ BearSSL::WiFiClientSecure* mqtt_tls; BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 String mqtt_rootCA; +String mqtt_fingerprint; # endif // ifdef USE_MQTT_TLS PubSubClient MQTTclient(mqtt); diff --git a/src/src/Globals/MQTT.h b/src/src/Globals/MQTT.h index 3cf7ff14b3..d73894f02b 100644 --- a/src/src/Globals/MQTT.h +++ b/src/src/Globals/MQTT.h @@ -33,6 +33,8 @@ extern BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 extern String mqtt_rootCA; +extern String mqtt_fingerprint; + # endif // ifdef USE_MQTT_TLS extern PubSubClient MQTTclient; extern bool MQTTclient_should_reconnect; diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 4df93d332d..77a49d21cb 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -1081,7 +1081,7 @@ String SaveCertificate(const String& fname, const String& certificate) return SaveToFile(fname.c_str(), 0, (const uint8_t *)certificate.c_str(), certificate.length() + 1); } -String LoadCertificate(const String& fname, String& certificate) +String LoadCertificate(const String& fname, String& certificate, bool cleanup) { bool changed = false; if (fileExists(fname)) { @@ -1115,15 +1115,17 @@ String LoadCertificate(const String& fname, String& certificate) } f.close(); - if (!cleanupCertificate(certificate, changed)) { - certificate.clear(); - #ifndef BUILD_NO_DEBUG - log += F(" ERROR, Invalid certificate format"); - #endif - addLog(LOG_LEVEL_ERROR, log); - return log; - } else if (changed) { - //return SaveCertificate(fname, certificate); + if (cleanup) { + if (!cleanupCertificate(certificate, changed)) { + certificate.clear(); + #ifndef BUILD_NO_DEBUG + log += F(" ERROR, Invalid certificate format"); + #endif + addLog(LOG_LEVEL_ERROR, log); + return log; + } else if (changed) { + //return SaveCertificate(fname, certificate); + } } } diff --git a/src/src/Helpers/ESPEasy_Storage.h b/src/src/Helpers/ESPEasy_Storage.h index 620c041bbe..e450c7917b 100644 --- a/src/src/Helpers/ESPEasy_Storage.h +++ b/src/src/Helpers/ESPEasy_Storage.h @@ -177,7 +177,7 @@ String LoadNotificationSettings(int NotificationIndex, uint8_t *memAddress, int The content will be stripped from unusable character like quotes, spaces etc. \*********************************************************************************************/ String SaveCertificate(const String& fname, const String& certificate); -String LoadCertificate(const String& fname, String& certificate); +String LoadCertificate(const String& fname, String& certificate, bool cleanup = true); /********************************************************************************************\ diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.h b/src/src/Helpers/ESPEasy_WiFiClientSecure.h index 0ddaffc20f..b5241bfda5 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.h +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.h @@ -69,6 +69,7 @@ class ESPEasy_WiFiClientSecure : public WiFiClient void stop(); uint8_t connected(); int lastError(char *buf, const size_t size); + void clearLastError() { _lastError = 0; } void setInsecure(); // Don't validate the chain, just accept whatever is given. VERY INSECURE! void setPreSharedKey(const char *pskIdent, const char *psKey); // psKey in Hex void setCACert(const char *rootCA); diff --git a/src/src/Helpers/_CPlugin_Helper_webform.cpp b/src/src/Helpers/_CPlugin_Helper_webform.cpp index ebc493a272..07663abbfe 100644 --- a/src/src/Helpers/_CPlugin_Helper_webform.cpp +++ b/src/src/Helpers/_CPlugin_Helper_webform.cpp @@ -21,7 +21,8 @@ const __FlashStringHelper * toString(ControllerSettingsStruct::VarType parameter case ControllerSettingsStruct::CONTROLLER_HOSTNAME: return F("Controller Hostname"); case ControllerSettingsStruct::CONTROLLER_IP: return F("Controller IP"); case ControllerSettingsStruct::CONTROLLER_PORT: return F("Controller Port"); - case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: return F("Use TLS"); + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: return F("Use TLS"); + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT: return F("Store Fingerprint"); case ControllerSettingsStruct::CONTROLLER_USER: return F("Controller User"); case ControllerSettingsStruct::CONTROLLER_PASS: return F("Controller Password"); @@ -118,6 +119,26 @@ void addControllerEnabledForm(controllerIndex_t controllerindex) { addFormCheckBox(displayName, internalName, Settings.ControllerEnabled[controllerindex]); } +void addCertificateFileNote(const ControllerSettingsStruct& ControllerSettings, const String& description) { + #ifdef USE_MQTT_TLS + const String certFile = ControllerSettings.getCertificateFilename(); + if (!certFile.isEmpty()) + { + const String certFile = ControllerSettings.getCertificateFilename(); + String note = description; + note += F(" "); + note += certFile; + note += F(" "); + if (fileExists(certFile)) { + note += F("(File exists)"); + } else { + note += F("(Not found)"); + } + addFormNote(note); + } + #endif +} + void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettings, controllerIndex_t controllerindex, ControllerSettingsStruct::VarType varType) { protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerindex); if (!validProtocolIndex(ProtocolIndex)) { @@ -158,35 +179,33 @@ void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettin { #ifdef USE_MQTT_TLS const int choice = static_cast(ControllerSettings.TLStype()); - #define NR_MQTT_TLS_TYPES 3 + #define NR_MQTT_TLS_TYPES 4 const __FlashStringHelper * options[NR_MQTT_TLS_TYPES] = { toString(TLS_types::NoTLS), // toString(TLS_types::TLS_PSK), toString(TLS_types::TLS_CA_CERT), + toString(TLS_types::TLS_FINGERPRINT), toString(TLS_types::TLS_insecure) }; const int indices[NR_MQTT_TLS_TYPES] = { static_cast(TLS_types::NoTLS), // static_cast(TLS_types::TLS_PSK), static_cast(TLS_types::TLS_CA_CERT), + static_cast(TLS_types::TLS_FINGERPRINT), static_cast(TLS_types::TLS_insecure) }; addFormSelector(displayName, internalName, NR_MQTT_TLS_TYPES, options, indices, choice, true); #undef NR_MQTT_TLS_TYPES - const String certFile = ControllerSettings.getCertificateFilename(); - if (!certFile.isEmpty()) - { - const String certFile = ControllerSettings.getCertificateFilename(); - String note = F("Certificate or PSK must be stored on the filesystem in "); - note += certFile; - note += F(" "); - if (fileExists(certFile)) { - note += F("(File exists)"); - } else { - note += F("(Not found)"); - } - addFormNote(note); - } + addCertificateFileNote(ControllerSettings, F("Certificate or PSK must be stored on the filesystem in")); + #endif + break; + } + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT: + { + #ifdef USE_MQTT_TLS + const bool saveDisabled = fileExists(ControllerSettings.getCertificateFilename()); + addFormCheckBox(displayName, internalName, false, saveDisabled); + addCertificateFileNote(ControllerSettings, F("Store fingerprint in")); #endif break; } @@ -356,6 +375,24 @@ void saveControllerParameterForm(ControllerSettingsStruct & ControllerSet break; } + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT: + { + #ifdef USE_MQTT_TLS + if (isFormItemChecked(internalName)) { + String fingerprint; + if (GetTLSfingerprint(fingerprint)) { + if (ControllerSettings.UseDNS) { + fingerprint += '\n'; + fingerprint += ControllerSettings.getHost(); + } + SaveCertificate(ControllerSettings.getCertificateFilename(), fingerprint); + } + } + #endif + break; + } + + case ControllerSettingsStruct::CONTROLLER_USER: setControllerUser(controllerindex, ControllerSettings, webArg(internalName)); break; diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 0a91d32df9..f6400a6f70 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -443,23 +443,22 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addHtml(F("")); } { - uint8_t sha256_result[32] = {0}; - if (mqtt_tls->getFingerprintSHA256(sha256_result)) { - String fingerprint; - fingerprint.reserve(64); - for (size_t i = 0; i < 32; ++i) { - fingerprint += String(sha256_result[i], HEX); - } - fingerprint.toLowerCase(); + String fingerprint; + if (GetTLSfingerprint(fingerprint)) { addFormTextBox(F("Certificate Fingerprint"), F("fingerprint"), fingerprint, 64, true); // ReadOnly + MakeControllerSettings(ControllerSettings); //-V522 + if (!AllocatedControllerSettings()) { + addHtmlError(F("Out of memory, cannot load page")); + } else { + LoadControllerSettings(controllerindex, ControllerSettings); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT); + } } } - - } #endif From a7750e8145a8a3d5c14a731a493ee207b2c6190e Mon Sep 17 00:00:00 2001 From: TD-er Date: Tue, 30 Nov 2021 00:40:47 +0100 Subject: [PATCH 036/100] [MQTT TLS] Add view of certificates + option to store --- .../ControllerQueue/DelayQueueElements.cpp | 4 + .../DataStructs/ControllerSettingsStruct.cpp | 12 ++- .../DataStructs/ControllerSettingsStruct.h | 3 + src/src/ESPEasyCore/Controller.cpp | 39 ++++++-- src/src/ESPEasyCore/Controller.h | 2 + src/src/Helpers/ESPEasy_Storage.cpp | 10 ++- src/src/Helpers/ESPEasy_WiFiClientSecure.cpp | 73 ++++++++++++++- src/src/Helpers/ESPEasy_WiFiClientSecure.h | 8 +- src/src/Helpers/ESPEasy_ssl_client.h | 1 + src/src/Helpers/_CPlugin_Helper_webform.cpp | 49 ++++++++-- src/src/WebServer/ControllerPage.cpp | 89 ++++++++++++++----- 11 files changed, 245 insertions(+), 45 deletions(-) diff --git a/src/src/ControllerQueue/DelayQueueElements.cpp b/src/src/ControllerQueue/DelayQueueElements.cpp index 0d1b5dfc66..1116d0545e 100644 --- a/src/src/ControllerQueue/DelayQueueElements.cpp +++ b/src/src/ControllerQueue/DelayQueueElements.cpp @@ -9,6 +9,9 @@ ControllerDelayHandlerStruct *MQTTDelayHandler = nullptr; bool init_mqtt_delay_queue(controllerIndex_t ControllerIndex, String& pubname, bool& retainFlag) { + // Make sure the controller is re-connecting with the current settings. + MQTTDisconnect(); + MakeControllerSettings(ControllerSettings); //-V522 if (!AllocatedControllerSettings()) { return false; @@ -34,6 +37,7 @@ bool init_mqtt_delay_queue(controllerIndex_t ControllerIndex, String& pubname, b void exit_mqtt_delay_queue() { if (MQTTDelayHandler != nullptr) { + MQTTDisconnect(); delete MQTTDelayHandler; MQTTDelayHandler = nullptr; } diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index ade97bf8b5..8cc1b6a3fe 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -328,13 +328,18 @@ void ControllerSettingsStruct::TLStype(TLS_types tls_type) } String ControllerSettingsStruct::getCertificateFilename() const +{ + return getCertificateFilename(TLStype()); +} + +String ControllerSettingsStruct::getCertificateFilename(TLS_types tls_type) const { String certFile = HostName; if (certFile.isEmpty()) { certFile = F(""); } - switch (TLStype()) { + switch (tls_type) { case TLS_types::NoTLS: case TLS_types::TLS_insecure: return EMPTY_STRING; @@ -353,6 +358,11 @@ String ControllerSettingsStruct::getCertificateFilename() const certFile += F(".fp"); break; } + + // Only use the last 29 bytes of the filename + if (certFile.length() > 28) { + certFile = certFile.substring(certFile.length() - 28); + } return certFile; } \ No newline at end of file diff --git a/src/src/DataStructs/ControllerSettingsStruct.h b/src/src/DataStructs/ControllerSettingsStruct.h index 92592efd19..b56e142385 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.h +++ b/src/src/DataStructs/ControllerSettingsStruct.h @@ -66,6 +66,8 @@ struct ControllerSettingsStruct CONTROLLER_PORT, CONTROLLER_MQTT_TLS_TYPE, CONTROLLER_MQTT_TLS_STORE_FINGERPRINT, + CONTROLLER_MQTT_TLS_STORE_CERT, + CONTROLLER_MQTT_TLS_STORE_CACERT, CONTROLLER_USER, CONTROLLER_PASS, CONTROLLER_MIN_SEND_INTERVAL, @@ -154,6 +156,7 @@ struct ControllerSettingsStruct void TLStype(TLS_types tls_type); String getCertificateFilename() const; + String getCertificateFilename(TLS_types tls_type) const; boolean UseDNS; diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index c633ff61f0..6614941337 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -235,6 +235,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) case TLS_types::TLS_CA_CERT: { mqtt_rootCA.clear(); + /* { static int previousFree = FreeMem(); const int freemem = FreeMem(); @@ -250,6 +251,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) addLog(LOG_LEVEL_INFO, analyse); previousFree = freemem; } + */ if (mqtt_rootCA.isEmpty()) { LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_rootCA); @@ -433,18 +435,23 @@ bool MQTTConnect(controllerIndex_t controller_idx) updateMQTTclient_connected(); return false; } - String log = F("MQTT : Connected to broker with client ID: "); + if (loglevelActiveFor(LOG_LEVEL_INFO)) + { + String log = F("MQTT : Connected to broker with client ID: "); - log += clientid; - addLog(LOG_LEVEL_INFO, log); + log += clientid; + addLog(LOG_LEVEL_INFO, log); + } #ifdef USE_MQTT_TLS #ifdef ESP32 + if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log = F("MQTT : Peer certificate info: "); + String log = F("MQTT : Peer certificate info: "); + log += ControllerSettings.getHost(); + log += ' '; log += mqtt_tls->getPeerCertificateInfo(); addLog(LOG_LEVEL_INFO, log); - log.clear(); } #endif #endif @@ -452,9 +459,12 @@ bool MQTTConnect(controllerIndex_t controller_idx) parseSystemVariables(subscribeTo, false); MQTTclient.subscribe(subscribeTo.c_str()); - log = F("Subscribed to: "); - log += subscribeTo; - addLog(LOG_LEVEL_INFO, log); + if (loglevelActiveFor(LOG_LEVEL_INFO)) + { + String log = F("Subscribed to: "); + log += subscribeTo; + addLog(LOG_LEVEL_INFO, log); + } updateMQTTclient_connected(); statusLED(true); @@ -797,6 +807,19 @@ bool GetTLSfingerprint(String& fp) return false; } +bool GetTLS_Certificate(String& cert, bool caRoot) +{ + #ifdef ESP32 + if (MQTTclient_connected && mqtt_tls != nullptr) { + String subject; + if (mqtt_tls->getPeerCertificate(cert, subject, caRoot) == 0) { + return true; + } + } + #endif + return false; +} + #endif #endif // USES_MQTT diff --git a/src/src/ESPEasyCore/Controller.h b/src/src/ESPEasyCore/Controller.h index e14ac48748..4529d17375 100644 --- a/src/src/ESPEasyCore/Controller.h +++ b/src/src/ESPEasyCore/Controller.h @@ -73,6 +73,8 @@ void MQTTStatus(struct EventStruct *event, const String& status); #ifdef USE_MQTT_TLS bool GetTLSfingerprint(String& fp); +bool GetTLS_Certificate(String& cert, bool caRoot); + #endif #endif //USES_MQTT diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 77a49d21cb..3618fe6a21 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -1042,7 +1042,7 @@ bool cleanupCertificate(String & certificate, bool &changed) for (int i = 0; i < 4 && last_pos != -1; ++i) { dash_pos[i] = certificate.indexOf(F("-----"), last_pos); last_pos = dash_pos[i] + 5; - addLog(LOG_LEVEL_INFO, String(F(" dash_pos: ")) + String(dash_pos[i])); +// addLog(LOG_LEVEL_INFO, String(F(" dash_pos: ")) + String(dash_pos[i])); } if (last_pos == -1) return false; @@ -1175,7 +1175,9 @@ String InitFile(SettingsType::SettingsFileEnum file_type) \*********************************************************************************************/ String SaveToFile(const char *fname, int index, const uint8_t *memAddress, int datasize) { - return doSaveToFile(fname, index, memAddress, datasize, "r+"); + return doSaveToFile( + fname, index, memAddress, datasize, + fileExists(fname) ? "r+" : "w+"); } // See for mode description: https://github.com/esp8266/Arduino/blob/master/doc/filesystem.rst @@ -1226,7 +1228,9 @@ String doSaveToFile(const char *fname, int index, const uint8_t *memAddress, int if (f) { clearAllCaches(); SPIFFS_CHECK(f, fname); - SPIFFS_CHECK(f.seek(index, fs::SeekSet), fname); + if (index > 0) { + SPIFFS_CHECK(f.seek(index, fs::SeekSet), fname); + } const uint8_t *pointerToByteToSave = memAddress; for (int x = 0; x < datasize; x++) diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp index b9ee2e5e43..5d924ca8b0 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp @@ -22,6 +22,9 @@ #include #include +// FIXME TD-er: Feels wrong this needs to be included here to use mbedtls_pem_write_buffer +#include + #undef connect #undef write #undef read @@ -348,9 +351,11 @@ void ESPEasy_WiFiClientSecure::setAlpnProtocols(const char **alpn_protos) _alpn_protos = alpn_protos; } -String ESPEasy_WiFiClientSecure::getPeerCertificateInfo() +String ESPEasy_WiFiClientSecure::getPeerCertificateInfo(const mbedtls_x509_crt* peer) { - const mbedtls_x509_crt* peer = getPeerCertificate(); + if (peer == nullptr) { + peer = getPeerCertificate(); + } String res; if (peer != nullptr) { char buf[1024] = {0}; @@ -365,4 +370,68 @@ String ESPEasy_WiFiClientSecure::getPeerCertificateInfo() } return res; } + +int ESPEasy_WiFiClientSecure::getPeerCertificate(String& pem, String& subject, bool caRoot) +{ + const mbedtls_x509_crt *chain; + + chain = getPeerCertificate(); + + int error {0}; + bool done = false; + while (chain != nullptr && error == 0 && !done) { + if (!caRoot || (chain->ca_istrue && chain->next == nullptr)) { + done = true; + error = ESPEasy_WiFiClientSecure::cert_to_pem(chain, pem, subject); + } + chain = chain->next; + } + return error; +} + +int ESPEasy_WiFiClientSecure::cert_to_pem(const mbedtls_x509_crt *crt, String& pem, String& subject) +{ + const String pem_begin_crt = F("-----BEGIN CERTIFICATE-----\n"); + const String pem_end_crt = F("-----END CERTIFICATE-----"); + pem.clear(); + subject.clear(); + + const mbedtls_asn1_named_data* common_name = &crt->subject; + while (common_name != nullptr) { + // While iterating through DN objects, check for CN object + if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &common_name->oid)) + { + + subject.reserve(common_name->val.len); + const unsigned char* p = common_name->val.p; + for (auto i = 0; i < common_name->val.len; ++i, ++p) { + subject += static_cast(*p); + } + } + + // Fetch next DN object + common_name = common_name->next; + } + + size_t written{}; + + const size_t buffer_size = + pem_begin_crt.length() + + pem_end_crt.length() + + 2* crt->raw.len; + + std::vector pem_buf; + pem_buf.resize(buffer_size); + int ret = mbedtls_pem_write_buffer( + pem_begin_crt.c_str(), pem_end_crt.c_str(), + crt->raw.p, crt->raw.len, + &pem_buf[0], buffer_size, &written); + if (ret == 0) { + pem.reserve(written); + for (auto i = 0; i < written; ++i) { + pem += static_cast(pem_buf[i]); + } + } + return ret; +} #endif \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.h b/src/src/Helpers/ESPEasy_WiFiClientSecure.h index b5241bfda5..a35fa92bfb 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.h +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.h @@ -81,10 +81,16 @@ class ESPEasy_WiFiClientSecure : public WiFiClient bool verify(const char* fingerprint, const char* domain_name); void setHandshakeTimeout(unsigned long handshake_timeout); void setAlpnProtocols(const char **alpn_protos); - String getPeerCertificateInfo(); + String getPeerCertificateInfo(const mbedtls_x509_crt* crt = nullptr); const mbedtls_x509_crt* getPeerCertificate() { return mbedtls_ssl_get_peer_cert(&sslclient->ssl_ctx); }; bool getFingerprintSHA256(uint8_t sha256_result[32]) { return get_peer_fingerprint(sslclient, sha256_result); }; + int getPeerCertificate(String& pem, String& subject, bool caRoot); + + // See: https://stackoverflow.com/a/63730321/8708166 + static int cert_to_pem(const mbedtls_x509_crt *crt, String& pem, String& subject); + + int setTimeout(uint32_t seconds){ return 0; } operator bool() diff --git a/src/src/Helpers/ESPEasy_ssl_client.h b/src/src/Helpers/ESPEasy_ssl_client.h index 5c612c3ae7..7f6eebf034 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.h +++ b/src/src/Helpers/ESPEasy_ssl_client.h @@ -46,5 +46,6 @@ int get_ssl_receive(ESPEasy_sslclient_context *ssl_client, uint8_t *data, int le bool verify_ssl_fingerprint(ESPEasy_sslclient_context *ssl_client, const char* fp, const char* domain_name); bool verify_ssl_dn(ESPEasy_sslclient_context *ssl_client, const char* domain_name); bool get_peer_fingerprint(ESPEasy_sslclient_context *ssl_client, uint8_t sha256[32]); + #endif #endif \ No newline at end of file diff --git a/src/src/Helpers/_CPlugin_Helper_webform.cpp b/src/src/Helpers/_CPlugin_Helper_webform.cpp index 07663abbfe..cf9be9f9d0 100644 --- a/src/src/Helpers/_CPlugin_Helper_webform.cpp +++ b/src/src/Helpers/_CPlugin_Helper_webform.cpp @@ -23,6 +23,9 @@ const __FlashStringHelper * toString(ControllerSettingsStruct::VarType parameter case ControllerSettingsStruct::CONTROLLER_PORT: return F("Controller Port"); case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: return F("Use TLS"); case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT: return F("Store Fingerprint"); + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CERT: return F("Store Certificate"); + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CACERT: return F("Store CA Certificate"); + case ControllerSettingsStruct::CONTROLLER_USER: return F("Controller User"); case ControllerSettingsStruct::CONTROLLER_PASS: return F("Controller Password"); @@ -119,12 +122,11 @@ void addControllerEnabledForm(controllerIndex_t controllerindex) { addFormCheckBox(displayName, internalName, Settings.ControllerEnabled[controllerindex]); } -void addCertificateFileNote(const ControllerSettingsStruct& ControllerSettings, const String& description) { +void addCertificateFileNote(const ControllerSettingsStruct& ControllerSettings, const String& description, TLS_types tls_type) { #ifdef USE_MQTT_TLS - const String certFile = ControllerSettings.getCertificateFilename(); + const String certFile = ControllerSettings.getCertificateFilename(tls_type); if (!certFile.isEmpty()) { - const String certFile = ControllerSettings.getCertificateFilename(); String note = description; note += F(" "); note += certFile; @@ -196,19 +198,38 @@ void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettin }; addFormSelector(displayName, internalName, NR_MQTT_TLS_TYPES, options, indices, choice, true); #undef NR_MQTT_TLS_TYPES - addCertificateFileNote(ControllerSettings, F("Certificate or PSK must be stored on the filesystem in")); + addCertificateFileNote(ControllerSettings, F("Certificate or PSK must be stored on the filesystem in"), ControllerSettings.TLStype()); #endif break; } case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT: { #ifdef USE_MQTT_TLS - const bool saveDisabled = fileExists(ControllerSettings.getCertificateFilename()); + const bool saveDisabled = fileExists(ControllerSettings.getCertificateFilename(TLS_types::TLS_FINGERPRINT)); addFormCheckBox(displayName, internalName, false, saveDisabled); - addCertificateFileNote(ControllerSettings, F("Store fingerprint in")); + addCertificateFileNote(ControllerSettings, F("Store fingerprint in"), TLS_types::TLS_FINGERPRINT); #endif break; } + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CERT: + // fall through + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CACERT: + { + #ifdef USE_MQTT_TLS + /* + const TLS_types tls_type = (varType == ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CACERT) ? + TLS_types::TLS_CA_CERT : TLS_types::TLS_CERT; + */ + const bool saveDisabled = fileExists(ControllerSettings.getCertificateFilename(TLS_types::TLS_CA_CERT)); + addFormCheckBox(displayName, internalName, false, saveDisabled); + if (saveDisabled) { + addUnit(F("File Exists")); + } + addCertificateFileNote(ControllerSettings, F("Store CA Certificate in"), TLS_types::TLS_CA_CERT); + #endif + break; + } + case ControllerSettingsStruct::CONTROLLER_USER: { const size_t fieldMaxLength = @@ -385,13 +406,27 @@ void saveControllerParameterForm(ControllerSettingsStruct & ControllerSet fingerprint += '\n'; fingerprint += ControllerSettings.getHost(); } - SaveCertificate(ControllerSettings.getCertificateFilename(), fingerprint); + SaveCertificate(ControllerSettings.getCertificateFilename(TLS_types::TLS_FINGERPRINT), fingerprint); } } #endif break; } + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CERT: + // fall through + case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CACERT: + { + #ifdef USE_MQTT_TLS + if (isFormItemChecked(internalName)) { + String cacert; + if (GetTLS_Certificate(cacert, true)) { + SaveCertificate(ControllerSettings.getCertificateFilename(TLS_types::TLS_CA_CERT), cacert); + } + } + #endif + break; + } case ControllerSettingsStruct::CONTROLLER_USER: setControllerUser(controllerindex, ControllerSettings, webArg(internalName)); diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index f6400a6f70..52118eab35 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -27,7 +27,6 @@ #include "../Helpers/StringConverter.h" - // ******************************************************************************** // Web Interface controller page // ******************************************************************************** @@ -434,34 +433,78 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex #ifdef ESP32 if (MQTTclient_connected && mqtt_tls != nullptr) { - addFormSubHeader(F("Peer Certificate")); - - { - addRowLabel(F("Certificate Info")); - addHtml(F("")); - } - { - String fingerprint; - if (GetTLSfingerprint(fingerprint)) { - addFormTextBox(F("Certificate Fingerprint"), - F("fingerprint"), - fingerprint, - 64, - true); // ReadOnly - MakeControllerSettings(ControllerSettings); //-V522 - if (!AllocatedControllerSettings()) { - addHtmlError(F("Out of memory, cannot load page")); - } else { - LoadControllerSettings(controllerindex, ControllerSettings); + MakeControllerSettings(ControllerSettings); //-V522 + if (!AllocatedControllerSettings()) { + addHtmlError(F("Out of memory, cannot load page")); + } else { + LoadControllerSettings(controllerindex, ControllerSettings); + + addFormSubHeader(F("Peer Certificate")); + + { + addRowLabel(F("Certificate Info")); + addHtml(F("")); + } + { + String fingerprint; + if (GetTLSfingerprint(fingerprint)) { + addFormTextBox(F("Certificate Fingerprint"), + F("fingerprint"), + fingerprint, + 64, + true); // ReadOnly addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT); } } + addFormSubHeader(F("Peer Certificate Chain")); + { + // FIXME TD-er: Must wrap this in divs to be able to fold it by default. + const mbedtls_x509_crt *chain; + + chain = mqtt_tls->getPeerCertificate(); + + int error {0}; + while (chain != nullptr && error == 0) { + /* + const bool mustShow = !chain->ca_istrue || chain->next == nullptr; + if (mustShow) { + */ + String pem, subject; + error = ESPEasy_WiFiClientSecure::cert_to_pem(chain, pem, subject); + { + String label; + if (chain->ca_istrue) { + label = F("CA "); + } + label += F("Certificate "); + label += subject; + label += F(""); + addRowLabel(label); + } + if (error == 0) { + addHtml(F("")); + + addHtml(F("")); + } else { + addHtmlInt(error); + } + if (chain->ca_istrue && chain->next == nullptr) { + // Add checkbox to store CA cert + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CACERT); + } +// } + chain = chain->next; + } + } } } #endif - } #endif } From c23a07d62f54ab0ef17dbd0ed811921b802c618b Mon Sep 17 00:00:00 2001 From: TD-er Date: Tue, 8 Feb 2022 14:00:00 +0100 Subject: [PATCH 037/100] [Webserver] Fix merge issue (MQTT TLS) --- src/src/WebServer/WebServer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/src/WebServer/WebServer.cpp b/src/src/WebServer/WebServer.cpp index 3dfe586618..2e81800d1c 100644 --- a/src/src/WebServer/WebServer.cpp +++ b/src/src/WebServer/WebServer.cpp @@ -91,6 +91,7 @@ void sendHeadandTail(const __FlashStringHelper * tmplName, boolean Tail, boolean String fileName = tmplName; fileName += F(".htm"); + fs::File f = tryOpenFile(fileName, "r"); WebTemplateParser templateParser(Tail, rebooting); if (f) { From 821af361a893c364b066f8c4fae94c85d8dd0e1d Mon Sep 17 00:00:00 2001 From: TD-er Date: Tue, 8 Feb 2022 17:03:29 +0100 Subject: [PATCH 038/100] [Merge Fix] Restore P095 and P096 from mega branch Was by accident merged in the wrong direction. --- src/_P095_ILI9341.ino | 13 ------------- src/_P096_eInk.ino | 9 --------- 2 files changed, 22 deletions(-) diff --git a/src/_P095_ILI9341.ino b/src/_P095_ILI9341.ino index a8cb5fc05e..9fb383959a 100644 --- a/src/_P095_ILI9341.ino +++ b/src/_P095_ILI9341.ino @@ -246,10 +246,6 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) TFT_Settings.address_tft_dc = PIN(1); TFT_Settings.address_tft_rst = PIN(2); TFT_Settings.rotation = PCONFIG(1); - if (tft != nullptr) { - delete tft; - tft = nullptr; - } initPluginTaskData(event->TaskIndex, new (std::nothrow) P095_data_struct( @@ -268,15 +264,6 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) break; } - case PLUGIN_EXIT: - { - if (tft != nullptr) { - delete tft; - tft = nullptr; - } - break; - } - case PLUGIN_WRITE: { String tmpString = String(string); diff --git a/src/_P096_eInk.ino b/src/_P096_eInk.ino index 74adafe53d..083a18d493 100644 --- a/src/_P096_eInk.ino +++ b/src/_P096_eInk.ino @@ -305,15 +305,6 @@ boolean Plugin_096(uint8_t function, struct EventStruct *event, String& string) break; } - case PLUGIN_EXIT: - { - if (eInkScreen != nullptr) { - delete eInkScreen; - eInkScreen = nullptr; - } - break; - } - case PLUGIN_WRITE: { #ifndef BUILD_NO_DEBUG From d51ace5c103fbb0ae911e1ef6a8569ab0d147850 Mon Sep 17 00:00:00 2001 From: TD-er Date: Tue, 8 Feb 2022 18:04:46 +0100 Subject: [PATCH 039/100] [MQTT TLS] Fix missing include mbedtls/oid.h --- src/src/Helpers/ESPEasy_WiFiClientSecure.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp index 5d924ca8b0..1ee80eaba5 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp @@ -22,8 +22,10 @@ #include #include + // FIXME TD-er: Feels wrong this needs to be included here to use mbedtls_pem_write_buffer #include +#include #undef connect #undef write From 7a06dc4acd496c20ab6f0ea447b433a53e717175 Mon Sep 17 00:00:00 2001 From: TD-er Date: Tue, 8 Feb 2022 18:31:03 +0100 Subject: [PATCH 040/100] [MQTT TLS] Fix missing include --- src/src/Helpers/ESPEasy_WiFiClientSecure.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp index 1ee80eaba5..52e4065cb0 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp @@ -27,6 +27,8 @@ #include #include +#include + #undef connect #undef write #undef read From 90b06ea2cc1b33811f3a6d2fa0097b57db5d15d0 Mon Sep 17 00:00:00 2001 From: TD-er Date: Fri, 22 Apr 2022 09:46:26 +0200 Subject: [PATCH 041/100] [Build] Fix merge issue. --- src/src/ESPEasyCore/Controller.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 8118a689e1..96f3a07651 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -345,8 +345,6 @@ bool MQTTConnect(controllerIndex_t controller_idx) bool willRetain = ControllerSettings.mqtt_willRetain() && ControllerSettings.mqtt_sendLWT(); bool cleanSession = ControllerSettings.mqtt_cleanSession(); // As suggested here: - mqtt_last_connect_attempt.setNow(); - if (MQTTclient_should_reconnect) { addLog(LOG_LEVEL_ERROR, F("MQTT : Intentional reconnect")); } From eec2a4e5a3fc059b764526e2da6031b050147b29 Mon Sep 17 00:00:00 2001 From: TD-er Date: Mon, 2 May 2022 21:24:01 +0200 Subject: [PATCH 042/100] [MQTT TLS] Fix build issues --- platformio_core_defs.ini | 2 ++ src/src/ESPEasyCore/Controller.cpp | 15 ++++++++++----- src/src/Helpers/ESPEasy_ssl_client.cpp | 3 ++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index 948010d823..f5efa84a50 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -170,6 +170,8 @@ build_flags = -DESP32_STAGE platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.3rc1/platform-espressif32-2.0.3new.zip platform_packages = build_flags = -DESP32_STAGE + -I$PROJECT_DIR/include + -include "sdkconfig.h" [core_esp32_3_5_0] platform = espressif32 @ 3.5.0 diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 96f3a07651..e8117ac6e6 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -258,7 +258,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) } */ - if (mqtt_rootCA.isEmpty()) { + if (mqtt_rootCA.isEmpty() && mqtt_tls != nullptr) { LoadCertificate(ControllerSettings.getCertificateFilename(), mqtt_rootCA); if (mqtt_rootCA.isEmpty()) { // Fingerprint must be of some minimal length to continue. @@ -296,17 +296,21 @@ bool MQTTConnect(controllerIndex_t controller_idx) addLog(LOG_LEVEL_ERROR, mqtt_tls_last_errorstr); return false; } - mqtt_tls->setInsecure(); + if (mqtt_tls != nullptr) { + mqtt_tls->setInsecure(); + } break; } case TLS_types::TLS_insecure: { mqtt_rootCA.clear(); - mqtt_tls->setInsecure(); + if (mqtt_tls != nullptr) { + mqtt_tls->setInsecure(); + } break; } } - if (TLS_type != TLS_types::NoTLS) { + if (TLS_type != TLS_types::NoTLS && mqtt_tls != nullptr) { // Certificate expiry not enabled in Mbed TLS. // mqtt_tls->setX509Time(node_time.getUnixTime()); mqtt_tls->setTimeout(ControllerSettings.ClientTimeout); @@ -377,6 +381,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) count_connection_results(MQTTresult, F("MQTT : Broker "), controller_number); #ifdef USE_MQTT_TLS + if (mqtt_tls != nullptr) { char buf[128] = {0}; #ifdef ESP8266 @@ -452,7 +457,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) #ifdef USE_MQTT_TLS #ifdef ESP32 - if (loglevelActiveFor(LOG_LEVEL_INFO)) + if (mqtt_tls != nullptr && loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("MQTT : Peer certificate info: "); log += ControllerSettings.getHost(); diff --git a/src/src/Helpers/ESPEasy_ssl_client.cpp b/src/src/Helpers/ESPEasy_ssl_client.cpp index bf8ff5dbd6..7e20438a52 100644 --- a/src/src/Helpers/ESPEasy_ssl_client.cpp +++ b/src/src/Helpers/ESPEasy_ssl_client.cpp @@ -22,10 +22,11 @@ #include #include -#ifndef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED +#if !defined(MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED) && !defined(MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED) # warning "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher" #else + const char *ESPEasy_pers = "esp32-tls"; static int _handle_error(int err, const char * function, int line) From a63fc9772f706f3ebd3c9347e2b3ef87fa575e2d Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 12 Jun 2022 11:45:54 +0200 Subject: [PATCH 043/100] [MQTT TLS] Disable fingerprint verify on ESP8266 as not implemented --- src/src/ESPEasyCore/Controller.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index e8117ac6e6..22eec8d33a 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -393,6 +393,8 @@ bool MQTTConnect(controllerIndex_t controller_idx) #endif mqtt_tls_last_errorstr = buf; } + #ifdef ESP32 + // FIXME TD-er: There seems to be no verify function in BearSSL used on ESP8266 if (TLS_type == TLS_types::TLS_FINGERPRINT) { // Check fingerprint @@ -423,6 +425,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) } } } + #endif #endif From 3b3ac15c283c949e3d86333568429437a2f10482 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 12 Jun 2022 12:01:01 +0200 Subject: [PATCH 044/100] [MQTT TLS] Disable USE_MQTT_TLS for LIMIT_BUILD_SIZE and 1M builds --- src/src/CustomBuild/define_plugin_sets.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index a9906a5dea..f7f6228cef 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -1869,4 +1869,10 @@ To create/register a plugin, you have to : #endif #endif +#if defined(LIMIT_BUILD_SIZE) || defined(ESP8266_1M) + #ifdef USE_MQTT_TLS + #undef USE_MQTT_TLS + #endif +#endif + #endif // CUSTOMBUILD_DEFINE_PLUGIN_SETS_H \ No newline at end of file From ff7f8675e9b705ad8389603ab7d0386a6baa2629 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 12 Jun 2022 12:21:22 +0200 Subject: [PATCH 045/100] [MQTT TLS] Disable TLS for ESP8266 completely --- src/src/CustomBuild/define_plugin_sets.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index f7f6228cef..19dd2f7665 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -1869,7 +1869,9 @@ To create/register a plugin, you have to : #endif #endif -#if defined(LIMIT_BUILD_SIZE) || defined(ESP8266_1M) +#ifdef ESP8266 +// It just doesn't work on ESP8266, too slow, too high memory requirements +//#if defined(LIMIT_BUILD_SIZE) || defined(ESP8266_1M) #ifdef USE_MQTT_TLS #undef USE_MQTT_TLS #endif From 1a105617a54a0dca90b5b529067db6b56ba43c9d Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 16 Oct 2022 17:01:54 +0200 Subject: [PATCH 046/100] [MQTT TLS] Rename USE_MQTT_TLS to FEATURE_MQTT_TLS --- src/_C002.cpp | 2 +- src/_C005.cpp | 2 +- src/_C006.cpp | 2 +- src/_C014.cpp | 2 +- src/src/CustomBuild/define_plugin_sets.h | 9 +++++++-- src/src/DataStructs/ControllerSettingsStruct.cpp | 4 ++-- src/src/ESPEasyCore/Controller.cpp | 14 +++++++------- src/src/ESPEasyCore/Controller.h | 2 +- src/src/Globals/MQTT.cpp | 4 ++-- src/src/Globals/MQTT.h | 8 ++++---- src/src/Helpers/_CPlugin_Helper_webform.cpp | 14 +++++++------- src/src/WebServer/ControllerPage.cpp | 8 ++++---- tools/pio/pre_custom_esp32.py | 2 +- tools/pio/pre_custom_esp82xx.py | 2 +- 14 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/_C002.cpp b/src/_C002.cpp index 31dd6b5e9d..47244a3b97 100644 --- a/src/_C002.cpp +++ b/src/_C002.cpp @@ -37,7 +37,7 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& Protocol[protocolCount].usesExtCreds = true; Protocol[protocolCount].defaultPort = 1883; Protocol[protocolCount].usesID = true; - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS Protocol[protocolCount].usesTLS = true; #endif break; diff --git a/src/_C005.cpp b/src/_C005.cpp index 5f3a117108..87b98dac81 100644 --- a/src/_C005.cpp +++ b/src/_C005.cpp @@ -36,7 +36,7 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& Protocol[protocolCount].usesExtCreds = true; Protocol[protocolCount].defaultPort = 1883; Protocol[protocolCount].usesID = false; - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS Protocol[protocolCount].usesTLS = true; #endif diff --git a/src/_C006.cpp b/src/_C006.cpp index cfb9c15927..a34f1342ea 100644 --- a/src/_C006.cpp +++ b/src/_C006.cpp @@ -36,7 +36,7 @@ bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String& Protocol[protocolCount].usesExtCreds = true; Protocol[protocolCount].defaultPort = 1883; Protocol[protocolCount].usesID = false; - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS Protocol[protocolCount].usesTLS = true; #endif diff --git a/src/_C014.cpp b/src/_C014.cpp index 6f23b27887..0b8bd86e42 100644 --- a/src/_C014.cpp +++ b/src/_C014.cpp @@ -183,7 +183,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& Protocol[protocolCount].usesExtCreds = true; Protocol[protocolCount].defaultPort = 1883; Protocol[protocolCount].usesID = false; - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS Protocol[protocolCount].usesTLS = true; #endif diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index efd49cce52..e4c0a65e80 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -2294,11 +2294,16 @@ To create/register a plugin, you have to : #endif #endif +#ifndef FEATURE_MQTT_TLS +#define FEATURE_MQTT_TLS 0 +#endif + #ifdef ESP8266 // It just doesn't work on ESP8266, too slow, too high memory requirements //#if defined(LIMIT_BUILD_SIZE) || defined(ESP8266_1M) - #ifdef USE_MQTT_TLS - #undef USE_MQTT_TLS + #if FEATURE_MQTT_TLS + #undef FEATURE_MQTT_TLS + #define FEATURE_MQTT_TLS 0 #endif #endif diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index 28130ffc1d..b5b4ddbfd7 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -78,8 +78,8 @@ void ControllerSettingsStruct::validate() { ZERO_TERMINATE(LWTMessageConnect); ZERO_TERMINATE(LWTMessageDisconnect); - #ifdef USES_MQTT - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT + #if FEATURE_MQTT_TLS if (TLStype() == TLS_types::NoTLS) { if (Port == 8883) { Port = 1883; diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 6c3e07a84e..782a90d20c 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -190,7 +190,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) if (MQTTclient.connected()) { MQTTclient.disconnect(); - # ifdef USE_MQTT_TLS + # if FEATURE_MQTT_TLS if (mqtt_tls != nullptr) { delete mqtt_tls; mqtt_tls = nullptr; @@ -205,7 +205,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) delay(0); uint16_t mqttPort = ControllerSettings.Port; -#ifdef USE_MQTT_TLS +#if FEATURE_MQTT_TLS mqtt_tls_last_errorstr.clear(); mqtt_tls_last_error = 0; const TLS_types TLS_type = ControllerSettings.TLStype(); @@ -431,7 +431,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) uint8_t controller_number = Settings.Protocol[controller_idx]; count_connection_results(MQTTresult, F("MQTT : Broker "), controller_number, connect_start_time); - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS if (mqtt_tls != nullptr) { char buf[128] = {0}; @@ -481,7 +481,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) #endif if (!MQTTresult) { - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS if ((mqtt_tls_last_error != 0) && loglevelActiveFor(LOG_LEVEL_ERROR)) { String log = F("MQTT : TLS error code: "); log += mqtt_tls_last_error; @@ -492,7 +492,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) #endif MQTTclient.disconnect(); - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS mqtt_tls->stop(); #endif @@ -507,7 +507,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) addLogMove(LOG_LEVEL_INFO, log); } - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS #ifdef ESP32 if (mqtt_tls != nullptr && loglevelActiveFor(LOG_LEVEL_INFO)) { @@ -841,7 +841,7 @@ void MQTTStatus(struct EventStruct *event, const String& status) } } -#ifdef USE_MQTT_TLS +#if FEATURE_MQTT_TLS bool GetTLSfingerprint(String& fp) { #ifdef ESP32 diff --git a/src/src/ESPEasyCore/Controller.h b/src/src/ESPEasyCore/Controller.h index 7ba94da154..228fd7df04 100644 --- a/src/src/ESPEasyCore/Controller.h +++ b/src/src/ESPEasyCore/Controller.h @@ -71,7 +71,7 @@ bool MQTTpublish(controllerIndex_t controller_idx, taskIndex_t taskIndex, Strin \*********************************************************************************************/ void MQTTStatus(struct EventStruct *event, const String& status); -#ifdef USE_MQTT_TLS +#if FEATURE_MQTT_TLS bool GetTLSfingerprint(String& fp); bool GetTLS_Certificate(String& cert, bool caRoot); diff --git a/src/src/Globals/MQTT.cpp b/src/src/Globals/MQTT.cpp index 0e47515e86..65e752e75f 100644 --- a/src/src/Globals/MQTT.cpp +++ b/src/src/Globals/MQTT.cpp @@ -7,7 +7,7 @@ // MQTT client WiFiClient mqtt; -# ifdef USE_MQTT_TLS +# if FEATURE_MQTT_TLS String mqtt_tls_last_errorstr; int32_t mqtt_tls_last_error = 0; @@ -20,7 +20,7 @@ BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 String mqtt_rootCA; String mqtt_fingerprint; -# endif // ifdef USE_MQTT_TLS +# endif // if FEATURE_MQTT_TLS PubSubClient MQTTclient(mqtt); bool MQTTclient_should_reconnect = true; diff --git a/src/src/Globals/MQTT.h b/src/src/Globals/MQTT.h index 073a075f49..4422fdfe63 100644 --- a/src/src/Globals/MQTT.h +++ b/src/src/Globals/MQTT.h @@ -10,17 +10,17 @@ # include # include -# ifdef USE_MQTT_TLS +# if FEATURE_MQTT_TLS # ifdef ESP32 # include "../Helpers/ESPEasy_WiFiClientSecure.h" # else # include # endif -# endif // ifdef USE_MQTT_TLS +# endif // if FEATURE_MQTT_TLS // MQTT client extern WiFiClient mqtt; -# ifdef USE_MQTT_TLS +# if FEATURE_MQTT_TLS extern String mqtt_tls_last_errorstr; extern int32_t mqtt_tls_last_error; # ifdef ESP32 @@ -35,7 +35,7 @@ extern BearSSL::X509List mqtt_X509List; extern String mqtt_rootCA; extern String mqtt_fingerprint; -# endif // ifdef USE_MQTT_TLS +# endif // if FEATURE_MQTT_TLS extern PubSubClient MQTTclient; extern bool MQTTclient_should_reconnect; extern bool MQTTclient_must_send_LWT_connected; diff --git a/src/src/Helpers/_CPlugin_Helper_webform.cpp b/src/src/Helpers/_CPlugin_Helper_webform.cpp index ef914b0325..d63fbd94ac 100644 --- a/src/src/Helpers/_CPlugin_Helper_webform.cpp +++ b/src/src/Helpers/_CPlugin_Helper_webform.cpp @@ -125,7 +125,7 @@ void addControllerEnabledForm(controllerIndex_t controllerindex) { } void addCertificateFileNote(const ControllerSettingsStruct& ControllerSettings, const String& description, TLS_types tls_type) { - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS const String certFile = ControllerSettings.getCertificateFilename(tls_type); if (!certFile.isEmpty()) { @@ -181,7 +181,7 @@ void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettin } case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: { - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS const int choice = static_cast(ControllerSettings.TLStype()); #define NR_MQTT_TLS_TYPES 4 const __FlashStringHelper * options[NR_MQTT_TLS_TYPES] = { @@ -206,7 +206,7 @@ void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettin } case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT: { - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS const bool saveDisabled = fileExists(ControllerSettings.getCertificateFilename(TLS_types::TLS_FINGERPRINT)); addFormCheckBox(displayName, internalName, false, saveDisabled); addCertificateFileNote(ControllerSettings, F("Store fingerprint in"), TLS_types::TLS_FINGERPRINT); @@ -217,7 +217,7 @@ void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettin // fall through case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CACERT: { - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS /* const TLS_types tls_type = (varType == ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CACERT) ? TLS_types::TLS_CA_CERT : TLS_types::TLS_CERT; @@ -390,7 +390,7 @@ void saveControllerParameterForm(ControllerSettingsStruct & ControllerSet break; case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: { - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS const int current = static_cast(ControllerSettings.TLStype()); const TLS_types tls_type = static_cast(getFormItemInt(internalName, current)); ControllerSettings.TLStype(tls_type); @@ -400,7 +400,7 @@ void saveControllerParameterForm(ControllerSettingsStruct & ControllerSet case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_FINGERPRINT: { - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS if (isFormItemChecked(internalName)) { String fingerprint; if (GetTLSfingerprint(fingerprint)) { @@ -419,7 +419,7 @@ void saveControllerParameterForm(ControllerSettingsStruct & ControllerSet // fall through case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_STORE_CACERT: { - #ifdef USE_MQTT_TLS + #if FEATURE_MQTT_TLS if (isFormItemChecked(internalName)) { String cacert; if (GetTLS_Certificate(cacert, true)) { diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 70857f854e..31dce95178 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -17,7 +17,7 @@ # include "../Globals/Protocol.h" # include "../Globals/Settings.h" -# ifdef USES_MQTT +# if FEATURE_MQTT # include "../Globals/MQTT.h" # endif @@ -332,7 +332,7 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex if (Protocol[ProtocolIndex].usesPort) { addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PORT); } - #ifdef USES_MQTT + #if FEATURE_MQTT if (Protocol[ProtocolIndex].usesMQTT && Protocol[ProtocolIndex].usesTLS) { addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE); addFormNote(F("Default ports: MQTT: 1883 / MQTT TLS: 8883")); @@ -440,13 +440,13 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex } } { -#ifdef USES_MQTT +#if FEATURE_MQTT if (Protocol[ProtocolIndex].usesMQTT) { addFormSubHeader(F("Connection Status")); addRowLabel(F("MQTT Client Connected")); addEnabled(MQTTclient_connected); -#ifdef USE_MQTT_TLS +#if FEATURE_MQTT_TLS if (Protocol[ProtocolIndex].usesTLS) { addRowLabel(F("Last Error")); addHtmlInt(mqtt_tls_last_error); diff --git a/tools/pio/pre_custom_esp32.py b/tools/pio/pre_custom_esp32.py index 8ad0ef63c8..51c480738c 100644 --- a/tools/pio/pre_custom_esp32.py +++ b/tools/pio/pre_custom_esp32.py @@ -57,7 +57,7 @@ "-DUSES_C018", # TTN/RN2483 # "-DUSES_C015", # Blynk - "-DUSE_MQTT_TLS", + "-DFEATURE_MQTT_TLS=1", "-DFEATURE_EXT_RTC=1", "-DFEATURE_SD=1", "-DFEATURE_I2CMULTIPLEXER=1", diff --git a/tools/pio/pre_custom_esp82xx.py b/tools/pio/pre_custom_esp82xx.py index a404eb6a4b..1352a78f70 100644 --- a/tools/pio/pre_custom_esp82xx.py +++ b/tools/pio/pre_custom_esp82xx.py @@ -56,7 +56,7 @@ "-DUSES_C018", # TTN/RN2483 # "-DUSES_C015", # Blynk - "-DUSE_MQTT_TLS", + "-DFEATURE_MQTT_TLS=0", # "-DFEATURE_MDNS=1", # "-DFEATURE_SD=1", "-DFEATURE_EXT_RTC=1", From 47233d55f8d34bb2eee6661c6c5a30faffd106ef Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 16 Oct 2022 18:08:58 +0200 Subject: [PATCH 047/100] [MQTT TLS] Resize certificate info textareas --- src/src/Helpers/ESPEasy_WiFiClientSecure.cpp | 2 +- src/src/WebServer/ControllerPage.cpp | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp index 52e4065cb0..0388398295 100644 --- a/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp +++ b/src/src/Helpers/ESPEasy_WiFiClientSecure.cpp @@ -425,7 +425,7 @@ int ESPEasy_WiFiClientSecure::cert_to_pem(const mbedtls_x509_crt *crt, String& p 2* crt->raw.len; std::vector pem_buf; - pem_buf.resize(buffer_size); + pem_buf.resize(buffer_size, 0u); int ret = mbedtls_pem_write_buffer( pem_begin_crt.c_str(), pem_end_crt.c_str(), crt->raw.p, crt->raw.len, diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 31dce95178..3bbb03eb46 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -464,10 +464,14 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addFormSubHeader(F("Peer Certificate")); { - addRowLabel(F("Certificate Info")); - addHtml(F("")); + addFormTextArea( + F("Certificate Info"), + F("certinfo"), + mqtt_tls->getPeerCertificateInfo(), + 0, + 10, + 0, + true); } { String fingerprint; From 80c189d1df2f28ef06c17a6bb4f2955650fb2eb5 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 16 Oct 2022 23:05:52 +0200 Subject: [PATCH 048/100] [MQTT TLS] Fix layout controller page --- .../DataStructs/ControllerSettingsStruct.cpp | 8 ++--- src/src/Helpers/StringConverter.cpp | 11 ++++++ src/src/Helpers/StringConverter.h | 4 +++ src/src/WebServer/ControllerPage.cpp | 34 ++++++++++++------- src/src/WebServer/Markup.cpp | 14 ++++++-- 5 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index b5b4ddbfd7..ce1d36884b 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -317,16 +317,12 @@ void ControllerSettingsStruct::useLocalSystemTime(bool value) TLS_types ControllerSettingsStruct::TLStype() const { // Store it in bits 12, 13, 14, 15 - const TLS_types tls_type = static_cast((VariousFlags >> 12) & 0xF); - return tls_type; + return static_cast(get4BitFromUL(VariousFlags, 12)); } void ControllerSettingsStruct::TLStype(TLS_types tls_type) { - const uint32_t mask = ~(0xF); - VariousFlags &= mask; // Clear the bits - const uint32_t tls_type_val = static_cast(tls_type) << 12; - VariousFlags |= tls_type_val; + set4BitToUL(VariousFlags, 12, static_cast(tls_type)); } String ControllerSettingsStruct::getCertificateFilename() const diff --git a/src/src/Helpers/StringConverter.cpp b/src/src/Helpers/StringConverter.cpp index 768fc07f91..aa7fdcd9b4 100644 --- a/src/src/Helpers/StringConverter.cpp +++ b/src/src/Helpers/StringConverter.cpp @@ -27,6 +27,17 @@ // -V::569 +unsigned int count_newlines(const String& str) +{ + unsigned int count = 0; + const size_t strlength = str.length(); + size_t pos = 0; + while (pos < strlength) { + if (str[pos] == '\n') ++count; + ++pos; + } + return count; +} /********************************************************************************************\ Convert a char string to integer diff --git a/src/src/Helpers/StringConverter.h b/src/src/Helpers/StringConverter.h index 2a9d1ce2c8..e0bea675a9 100644 --- a/src/src/Helpers/StringConverter.h +++ b/src/src/Helpers/StringConverter.h @@ -14,6 +14,10 @@ class IPAddress; // -V::569 + +unsigned int count_newlines(const String& str); + + /********************************************************************************************\ Concatenate using code which results in the smallest compiled code \*********************************************************************************************/ diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 3bbb03eb46..e24d234cde 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -465,12 +465,12 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex { addFormTextArea( - F("Certificate Info"), - F("certinfo"), + F("Certificate Info"), + F("certinfo"), mqtt_tls->getPeerCertificateInfo(), - 0, - 10, - 0, + -1, + -1, + -1, true); } { @@ -510,13 +510,23 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addRowLabel(label); } if (error == 0) { - addHtml(F("")); - - addHtml(F("")); + addTextArea( + F("peerCertInfo"), + mqtt_tls->getPeerCertificateInfo(chain), + -1, + -1, + -1, + true, + false); + + addTextArea( + F("pem"), + pem, + -1, + -1, + -1, + true, + false); } else { addHtmlInt(error); } diff --git a/src/src/WebServer/Markup.cpp b/src/src/WebServer/Markup.cpp index d26104ffca..5fbcebc54a 100644 --- a/src/src/WebServer/Markup.cpp +++ b/src/src/WebServer/Markup.cpp @@ -9,6 +9,7 @@ #include "../Helpers/Convert.h" #include "../Helpers/Hardware.h" +#include "../Helpers/StringConverter.h" #include "../Helpers/StringGenerator_GPIO.h" #include "../../ESPEasy_common.h" @@ -810,15 +811,22 @@ void addTextArea(const String & id, #endif // if FEATURE_TOOLTIPS ) { + if (rows < 0) { + rows = count_newlines(value) + 1; + } addHtml(F("