From e398685ba48e889503f344f182f1f7eb6434dd7a Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Tue, 3 Sep 2019 06:20:39 +0300 Subject: [PATCH] mqtt: generic secureclient configuration (#1873) * Rework MQTT sync client configuration * MQTT_LIBRARY_... * comments * fix not doing any checks for bearssl, remove afterconnected template for mqttclient * comments * comments * secure bool stub * typos, always include sc helpers * fix on_fingerprint return type * typo * typo * indent * tag is already in config obj * generate mqtt featureset string at compile time * reword again * trusted_root_ca --- code/espurna/config/general.h | 43 ++- code/espurna/config/prototypes.h | 27 +- code/espurna/config/types.h | 7 +- code/espurna/libs/SecureClientHelpers.h | 230 ++++++++++++++ code/espurna/mqtt.ino | 398 ++++++++++++------------ code/espurna/ota_httpupdate.ino | 14 +- 6 files changed, 482 insertions(+), 237 deletions(-) create mode 100644 code/espurna/libs/SecureClientHelpers.h diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index fa6cd3ad..475594be 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -789,8 +789,8 @@ // TODO: eventually should be replaced with pre-parsed structs, read directly from flash // (ref: https://github.com/earlephilhower/bearssl-esp8266/pull/14) // - // When enabled, current implementation includes "static/ota_secure_client_ca.h" with - // const char _ota_client_http_update_ca[] PROGMEM = "...PEM data..."; + // When enabled, current implementation includes "static/ota_client_trusted_root_ca.h" with + // const char _ota_client_trusted_root_ca[] PROGMEM = "...PEM data..."; // By default, using DigiCert root in "static/digicert_evroot_pem.h" (for https://github.com) #endif @@ -859,27 +859,36 @@ #ifndef MQTT_LIBRARY -#define MQTT_LIBRARY MQTT_ASYNC // Choose between: MQTT_ASYNC (AysncMQTTClient), MQTT_PUBSUB (PubSubClient), MQTT_ARDUINO (Arduino-MQTT) +#define MQTT_LIBRARY MQTT_LIBRARY_ASYNCMQTTCLIENT // MQTT_LIBRARY_ASYNCMQTTCLIENT (default, https://github.com/marvinroger/async-mqtt-client) + // MQTT_LIBRARY_PUBSUBCLIENT (https://github.com/knolleary/pubsubclient) + // MQTT_LIBRARY_ARDUINOMQTT (https://github.com/256dpi/arduino-mqtt) #endif +// ----------------------------------------------------------------------------- // MQTT OVER SSL -// Using MQTT over SSL works pretty well but generates problems with the web interface. -// It could be a good idea to use it in conjuntion with WEB_SUPPORT=0. -// Requires SECURE_CLIENT = SECURE_CLIENT_AXTLS or SECURE_CLIENT_BEARSSL and ESP8266 Arduino Core >= 2.4.0. +// ----------------------------------------------------------------------------- +// +// Requires SECURE_CLIENT set to SECURE_CLIENT_AXTLS or SECURE_CLIENT_BEARSSL +// It is recommended to use MQTT_LIBRARY_ARDUINOMQTT or MQTT_LIBRARY_PUBSUBCLIENT +// It is recommended to use SECURE_CLIENT_BEARSSL +// It is recommended to use ESP8266 Arduino Core >= 2.5.2 with SECURE_CLIENT_BEARSSL // -// You can use SSL with MQTT_LIBRARY=ASYNC (AsyncMqttClient library) -// but you might experience hiccups on the web interface, so my recommendation is: -// WEB_SUPPORT=0 +// Current version of MQTT_LIBRARY_ASYNCMQTTCLIENT only supports SECURE_CLIENT_AXTLS // -// If you use SSL with MQTT_LIBRARY=PUBSUB (PubSubClient library) or MQTT_LIBRARY=ARDUINO (Arduino-MQTT library) -// you will have to disable all the modules that use ESPAsyncTCP, that is: +// It is recommended to use WEB_SUPPORT=0 with either SECURE_CLIENT option, as there are miscellaneous problems when using them simultaneously +// (although, things might've improved, and I'd encourage to check whether this is true or not) +// +// When using MQTT_LIBRARY_PUBSUBCLIENT or MQTT_LIBRARY_ARDUINOMQTT, you will have to disable every module that uses ESPAsyncTCP: // ALEXA_SUPPORT=0, INFLUXDB_SUPPORT=0, TELNET_SUPPORT=0, THINGSPEAK_SUPPORT=0, DEBUG_TELNET_SUPPORT=0 and WEB_SUPPORT=0 +// Or, use "sync" versions instead (note that not every module has this option): +// THINGSPEAK_USE_ASYNC=0, TELNET_SERVER=TELNET_SERVER_WIFISERVER +// +// See SECURE_CLIENT_CHECK for all possible connection verification options. // -// You will need the fingerprint of your MQTT server, in order to prevent MITS attacks. -// To get a certificate fingerprint, run the following command: +// The simpliest way to verify SSL connection is to use fingerprinting. +// For example, to get Google's MQTT server certificate fingerprint, run the following command: // $ echo -n | openssl s_client -connect mqtt.googleapis.com:8883 2>&1 | openssl x509 -noout -fingerprint -sha1 | cut -d\= -f2 -// Note that this fingerprint changes with e.g. LetsEncrypt renewals or when the CSR changes. -// It's also possible to leave the fingerprint empty, the certificate is then always trusted. +// Note that fingerprint will change when certificate changes e.g. LetsEncrypt renewals or when the CSR updates #ifndef MQTT_SSL_ENABLED #define MQTT_SSL_ENABLED 0 // By default MQTT over SSL will not be enabled @@ -899,8 +908,8 @@ #ifndef MQTT_SECURE_CLIENT_INCLUDE_CA #define MQTT_SECURE_CLIENT_INCLUDE_CA 0 // Use user-provided CA. Only PROGMEM PEM option is supported. - // When enabled, current implementation includes "static/mqtt_secure_client_ca.h" with - // const char _mqtt_client_ca[] PROGMEM = "...PEM data..."; + // When enabled, current implementation includes "static/mqtt_client_trusted_root_ca.h" with + // const char _mqtt_client_trusted_root_ca[] PROGMEM = "...PEM data..."; // By default, using LetsEncrypt X3 root in "static/letsencrypt_isrgroot_pem.h" #endif diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index 22ee654e..7c53b3cb 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -183,6 +183,15 @@ void lightChannel(unsigned char id, unsigned char value); // MQTT // ----------------------------------------------------------------------------- +#if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT + #include + #include +#elif MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT + #include +#elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT + #include +#endif + using mqtt_callback_f = std::function; #if MQTT_SUPPORT @@ -190,13 +199,6 @@ using mqtt_callback_f = std::function; String mqttMagnitude(char * topic); #endif -#if MQTT_SECURE_CLIENT_INCLUDE_CA -#include "../static/mqtt_secure_client_ca.h" // Assumes this header file defines a _mqtt_client_ca[] PROGMEM = "...PEM data..." -#else -#include "../static/letsencrypt_isrgroot_pem.h" // Default to LetsEncrypt X3 certificate -#define _mqtt_client_ca _ssl_letsencrypt_isrg_x3_ca -#endif // MQTT_SECURE_CLIENT_INCLUDE_CA - // ----------------------------------------------------------------------------- // OTA // ----------------------------------------------------------------------------- @@ -213,17 +215,8 @@ using mqtt_callback_f = std::function; #endif #if SECURE_CLIENT != SECURE_CLIENT_NONE - #include - - #if OTA_SECURE_CLIENT_INCLUDE_CA - #include "../static/ota_secure_client_ca.h" - #else - #include "../static/digicert_evroot_pem.h" - #define _ota_client_http_update_ca _ssl_digicert_ev_root_ca - #endif - -#endif // SECURE_CLIENT_SUPPORT +#endif // SECURE_CLIENT != SECURE_CLIENT_NONE // ----------------------------------------------------------------------------- // RFM69 diff --git a/code/espurna/config/types.h b/code/espurna/config/types.h index d389b6ae..7be27baf 100644 --- a/code/espurna/config/types.h +++ b/code/espurna/config/types.h @@ -152,9 +152,10 @@ #define MQTT_DISCONNECT_EVENT 1 #define MQTT_MESSAGE_EVENT 2 -#define MQTT_ASYNC 0 -#define MQTT_ARDUINO 1 -#define MQTT_PUBSUB 2 +// MQTT_LIBRARY +#define MQTT_LIBRARY_ASYNCMQTTCLIENT 0 +#define MQTT_LIBRARY_ARDUINOMQTT 1 +#define MQTT_LIBRARY_PUBSUBCLIENT 2 //------------------------------------------------------------------------------ diff --git a/code/espurna/libs/SecureClientHelpers.h b/code/espurna/libs/SecureClientHelpers.h new file mode 100644 index 00000000..2fb4bf8b --- /dev/null +++ b/code/espurna/libs/SecureClientHelpers.h @@ -0,0 +1,230 @@ +// ----------------------------------------------------------------------------- +// WiFiClientSecure validation helpers +// ----------------------------------------------------------------------------- + +#pragma once + +#if SECURE_CLIENT != SECURE_CLIENT_NONE + +#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL +#include +#elif SECURE_CLIENT == SECURE_CLIENT_AXTLS +#include +#endif + +namespace SecureClientHelpers { + +using host_callback_f = std::function; +using check_callback_f = std::function; +using fp_callback_f = std::function; +using cert_callback_f = std::function; +using mfln_callback_f = std::function; + +#if SECURE_CLIENT == SECURE_CLIENT_AXTLS +using SecureClientClass = axTLS::WiFiClientSecure; + +struct SecureClientConfig { + SecureClientConfig(const char* tag, host_callback_f host_cb, check_callback_f check_cb, fp_callback_f fp_cb, bool debug = false) : + tag(tag), + on_host(host_cb), + on_check(check_cb), + on_fingerprint(fp_cb), + debug(debug) + {} + + String tag; + host_callback_f on_host; + check_callback_f on_check; + fp_callback_f on_fingerprint; + bool debug; +}; + +struct SecureClientChecks { + + SecureClientChecks(SecureClientConfig& config) : + config(config) + {} + + int getCheck() { + return (config.on_check) ? config.on_check() : (SECURE_CLIENT_CHECK); + } + + bool beforeConnected(SecureClientClass& client) { + return true; + } + + // Special condition for legacy client! + // Otherwise, we are required to connect twice. And it is deemed broken & deprecated anyways... + bool afterConnected(SecureClientClass& client) { + bool result = false; + + int check = getCheck(); + + if (check == SECURE_CLIENT_CHECK_NONE) { + if (config.debug) DEBUG_MSG_P(PSTR("[%s] !!! Secure connection will not be validated !!!\n"), config.tag.c_str()); + result = true; + } else if (check == SECURE_CLIENT_CHECK_FINGERPRINT) { + if (config.on_fingerprint) { + char _buffer[60] = {0}; + if (config.on_fingerprint && config.on_host && sslFingerPrintChar(config.on_fingerprint().c_str(), _buffer)) { + result = client.verify(_buffer, config.on_host().c_str()); + } + if (!result) DEBUG_MSG_P(PSTR("[%s] Wrong fingerprint, cannot connect\n"), config.tag.c_str()); + } + } else if (check == SECURE_CLIENT_CHECK_CA) { + if (config.debug) DEBUG_MSG_P(PSTR("[%s] CA verification is not supported with axTLS client\n"), config.tag.c_str()); + } + + return result; + } + + SecureClientConfig& config; + bool debug; + +}; +#endif // SECURE_CLIENT_AXTLS + +#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL + +using SecureClientClass = BearSSL::WiFiClientSecure; + +struct SecureClientConfig { + SecureClientConfig(const char* tag, check_callback_f check_cb, cert_callback_f cert_cb, fp_callback_f fp_cb, mfln_callback_f mfln_cb, bool debug = false) : + tag(tag), + on_check(check_cb), + on_certificate(cert_cb), + on_fingerprint(fp_cb), + on_mfln(mfln_cb), + debug(debug) + {} + + String tag; + check_callback_f on_check; + cert_callback_f on_certificate; + fp_callback_f on_fingerprint; + mfln_callback_f on_mfln; + bool debug; +}; + +struct SecureClientChecks { + + SecureClientChecks(SecureClientConfig& config) : + config(config) + {} + + int getCheck() { + return (config.on_check) ? config.on_check() : (SECURE_CLIENT_CHECK); + } + + bool prepareMFLN(SecureClientClass& client) { + const uint16_t requested_mfln = (config.on_mfln) ? config.on_mfln() : (SECURE_CLIENT_MFLN); + bool result = false; + switch (requested_mfln) { + // default, do nothing + case 0: + result = true; + break; + // match valid sizes only + case 512: + case 1024: + case 2048: + case 4096: + { + client.setBufferSizes(requested_mfln, requested_mfln); + result = true; + if (config.debug) { + DEBUG_MSG_P(PSTR("[%s] MFLN buffer size set to %u\n"), config.tag.c_str(), requested_mfln); + } + break; + } + default: + { + if (config.debug) { + DEBUG_MSG_P(PSTR("[%s] Warning: MFLN buffer size must be one of 512, 1024, 2048 or 4096\n"), config.tag.c_str()); + } + } + } + + return result; + } + + bool beforeConnected(SecureClientClass& client) { + int check = getCheck(); + bool settime = (check == SECURE_CLIENT_CHECK_CA); + + if (!ntpSynced() && settime) { + if (config.debug) DEBUG_MSG_P(PSTR("[%s] Time not synced! Cannot use CA validation\n"), config.tag.c_str()); + return false; + } + + prepareMFLN(client); + + if (check == SECURE_CLIENT_CHECK_NONE) { + if (config.debug) DEBUG_MSG_P(PSTR("[%s] !!! Secure connection will not be validated !!!\n"), config.tag.c_str()); + client.setInsecure(); + } else if (check == SECURE_CLIENT_CHECK_FINGERPRINT) { + uint8_t _buffer[20] = {0}; + if (config.on_fingerprint && sslFingerPrintArray(config.on_fingerprint().c_str(), _buffer)) { + client.setFingerprint(_buffer); + } + } else if (check == SECURE_CLIENT_CHECK_CA) { + client.setX509Time(ntpLocal2UTC(now())); + if (!certs.getCount()) { + if (config.on_certificate) certs.append(config.on_certificate()); + } + client.setTrustAnchors(&certs); + } + + return true; + } + + bool afterConnected(SecureClientClass&) { + return true; + } + + bool debug; + + SecureClientConfig& config; + + BearSSL::X509List certs; + +}; +#endif // SECURE_CLIENT_BEARSSL + +class SecureClient { + + public: + + SecureClient(SecureClientConfig& config) : + _config(config), + _checks(_config), + _client(std::make_unique()) + {} + + bool afterConnected() { + return _checks.afterConnected(get()); + } + + bool beforeConnected() { + return _checks.beforeConnected(get()); + } + + SecureClientClass& get() { + return *_client.get(); + } + + private: + + SecureClientConfig _config; + SecureClientChecks _checks; + std::unique_ptr _client; + +}; + +}; + +using SecureClientConfig = SecureClientHelpers::SecureClientConfig; +using SecureClientChecks = SecureClientHelpers::SecureClientChecks; +using SecureClient = SecureClientHelpers::SecureClient; + +#endif // SECURE_CLIENT != SECURE_CLIENT_NONE diff --git a/code/espurna/mqtt.ino b/code/espurna/mqtt.ino index 91fe1741..9025d6e4 100644 --- a/code/espurna/mqtt.ino +++ b/code/espurna/mqtt.ino @@ -3,6 +3,7 @@ MQTT MODULE Copyright (C) 2016-2019 by Xose Pérez +Updated secure client support by Niek van der Maas < mail at niekvandermaas dot nl> */ @@ -17,41 +18,46 @@ Copyright (C) 2016-2019 by Xose Pérez #include #include -#if MQTT_LIBRARY == MQTT_ASYNC // AsyncMqttClient - #include +#include "libs/SecureClientHelpers.h" + +#if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT + AsyncMqttClient _mqtt; -#else // Arduino-MQTT or PubSubClient + +#else // MQTT_LIBRARY_ARDUINOMQTT / MQTT_LIBRARY_PUBSUBCLIENT + WiFiClient _mqtt_client; - bool _mqtt_connected = false; - #include "WiFiClientSecure.h" +#if SECURE_CLIENT != SECURE_CLIENT_NONE + std::unique_ptr _mqtt_client_secure = nullptr; - #if SECURE_CLIENT == SECURE_CLIENT_AXTLS - using namespace axTLS; - #elif SECURE_CLIENT == SECURE_CLIENT_BEARSSL - using namespace BearSSL; - BearSSL::X509List *_ca_list = nullptr; - #endif + #if MQTT_SECURE_CLIENT_INCLUDE_CA + #include "static/mqtt_client_trusted_root_ca.h" // Assumes this header file defines a _mqtt_client_trusted_root_ca[] PROGMEM = "...PEM data..." + #else + #include "static/letsencrypt_isrgroot_pem.h" // Default to LetsEncrypt X3 certificate + #define _mqtt_client_trusted_root_ca _ssl_letsencrypt_isrg_x3_ca + #endif // MQTT_SECURE_CLIENT_INCLUDE_CA - WiFiClientSecure _mqtt_client_secure; +#endif // SECURE_CLIENT != SECURE_CLIENT_NONE - #if MQTT_LIBRARY == MQTT_ARDUINO // Using Arduino-MQTT - #include - #ifdef MQTT_MAX_PACKET_SIZE +#if MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT +#ifdef MQTT_MAX_PACKET_SIZE MQTTClient _mqtt(MQTT_MAX_PACKET_SIZE); - #else - MQTTClient _mqtt(); - #endif - #else // Using PubSubClient - #include +#else + MQTTClient _mqtt; +#endif +#elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT PubSubClient _mqtt; - #endif #endif +#endif // MQTT_LIBRARY == MQTT_ASYNCMQTTCLIENT + + bool _mqtt_enabled = MQTT_ENABLED; bool _mqtt_use_json = false; unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN; unsigned long _mqtt_last_connection = 0; +bool _mqtt_connected = false; bool _mqtt_connecting = false; unsigned char _mqtt_qos = MQTT_QOS; bool _mqtt_retain = MQTT_RETAIN; @@ -83,6 +89,115 @@ Ticker _mqtt_flush_ticker; // Private // ----------------------------------------------------------------------------- +#if SECURE_CLIENT == SECURE_CLIENT_AXTLS +SecureClientConfig _mqtt_sc_config { + "MQTT", + []() -> String { + return _mqtt_server; + }, + []() -> int { + return getSetting("mqttScCheck", MQTT_SECURE_CLIENT_CHECK).toInt(); + }, + []() -> String { + return getSetting("mqttfp", MQTT_SSL_FINGERPRINT); + }, + true +}; +#endif + +#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL +SecureClientConfig _mqtt_sc_config { + "MQTT", + []() -> int { + return getSetting("mqttScCheck", MQTT_SECURE_CLIENT_CHECK).toInt(); + }, + []() -> PGM_P { + return _mqtt_client_trusted_root_ca; + }, + []() -> String { + return getSetting("mqttfp", MQTT_SSL_FINGERPRINT); + }, + []() -> uint16_t { + return getSetting("mqttScMFLN", MQTT_SECURE_CLIENT_MFLN).toInt(); + }, + true +}; +#endif + + +#if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT + +void _mqttSetupAsyncClient(bool secure = false) { + + _mqtt.setServer(_mqtt_server.c_str(), _mqtt_port); + _mqtt.setClientId(_mqtt_clientid.c_str()); + _mqtt.setKeepAlive(_mqtt_keepalive); + _mqtt.setCleanSession(false); + _mqtt.setWill(_mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, MQTT_STATUS_OFFLINE); + + if (_mqtt_user.length() && _mqtt_pass.length()) { + DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user.c_str()); + _mqtt.setCredentials(_mqtt_user.c_str(), _mqtt_pass.c_str()); + } + + #if SECURE_CLIENT != SECURE_CLIENT_NONE + if (secure) { + DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n")); + _mqtt.setSecure(secure); + } + #endif // SECURE_CLIENT != SECURE_CLIENT_NONE + + _mqtt.connect(); + +} + +#endif // MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT + +#if (MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT) || (MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT) +bool _mqttSetupSyncClient(bool secure = false) { + + #if SECURE_CLIENT != SECURE_CLIENT_NONE + if (secure) { + if (!_mqtt_client_secure) _mqtt_client_secure = std::make_unique(_mqtt_sc_config); + return _mqtt_client_secure->beforeConnected(); + } + #endif + + return true; + +} + +bool _mqttConnectSyncClient(bool secure = false) { + bool result = false; + + #if MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT + _mqtt.begin(_mqtt_server.c_str(), _mqtt_port, (secure ? _mqtt_client_secure->get() : _mqtt_client)); + _mqtt.setWill(_mqtt_will.c_str(), MQTT_STATUS_OFFLINE, _mqtt_qos, _mqtt_retain); + result = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_user.c_str(), _mqtt_pass.c_str()); + #elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT + _mqtt.setClient(secure ? _mqtt_client_secure->get() : _mqtt_client); + _mqtt.setServer(_mqtt_server.c_str(), _mqtt_port); + + if (_mqtt_user.length() && _mqtt_pass.length()) { + DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user.c_str()); + result = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_user.c_str(), _mqtt_pass.c_str(), _mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, MQTT_STATUS_OFFLINE); + } else { + result = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, MQTT_STATUS_OFFLINE); + } + #endif + + #if SECURE_CLIENT != SECURE_CLIENT_NONE + if (result && secure) { + result = _mqtt_client_secure->afterConnected(); + } + #endif + + return result; +} + +#endif // (MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT) || (MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT) + + void _mqttConnect() { // Do not connect if disabled @@ -114,168 +229,24 @@ void _mqttConnect() { _mqtt_connecting = true; - #if MQTT_LIBRARY == MQTT_ASYNC - _mqtt.setServer(_mqtt_server.c_str(), _mqtt_port); - _mqtt.setClientId(_mqtt_clientid.c_str()); - _mqtt.setKeepAlive(_mqtt_keepalive); - _mqtt.setCleanSession(false); - _mqtt.setWill(_mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, MQTT_STATUS_OFFLINE); - if (_mqtt_user.length() && _mqtt_pass.length()) { - DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user.c_str()); - _mqtt.setCredentials(_mqtt_user.c_str(), _mqtt_pass.c_str()); - } - - #if SECURE_CLIENT != SECURE_CLIENT_NONE - - bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1; - _mqtt.setSecure(secure); - if (secure) { - DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n")); - int check = getSetting("mqttScCheck", MQTT_SECURE_CLIENT_CHECK).toInt(); - if (check == SECURE_CLIENT_CHECK_FINGERPRINT) { - DEBUG_MSG_P(PSTR("[MQTT] Using fingerprint verification, trying to connect\n")); - - unsigned char fp[20] = {0}; - if (sslFingerPrintArray(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) { - _mqtt.addServerFingerprint(fp); - } else { - DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint, cannot connect\n")); - _mqtt_last_connection = millis(); - return; - } - } else if (check == SECURE_CLIENT_CHECK_CA) { - DEBUG_MSG_P(PSTR("[MQTT] CA verification is not supported with AsyncMqttClient, cannot connect\n")); - _mqtt_last_connection = millis(); - return; - } else { - DEBUG_MSG_P(PSTR("[MQTT] !!! SSL connection will not be validated !!!\n")); - } - } - - #endif // SECURE_CLIENT != SECURE_CLIENT_NONE - - _mqtt.connect(); - - #else // Using PubSubClient or Arduino-MQTT - - bool verified = true; // Connection verified - bool secure = false; // Whether to use SSL - - #if SECURE_CLIENT != SECURE_CLIENT_NONE - - secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1; - if (secure) { - DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n")); - - // Use MFLN if configured and on BearSSL - #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL - uint16_t requested_mfln = getSetting("mqttScMFLN", MQTT_SECURE_CLIENT_MFLN).toInt(); - if (requested_mfln) { - bool supported = _mqtt_client_secure.probeMaxFragmentLength(_mqtt_server.c_str(), _mqtt_port, requested_mfln); - DEBUG_MSG_P(PSTR("[MQTT] MFLN buffer size %u supported: %s\n"), requested_mfln, supported ? "YES" : "NO"); - if (supported) { - _mqtt_client_secure.setBufferSizes(requested_mfln, requested_mfln); - } - } - #endif - - // Default verification: CA in case of BearSSL, fingerprint otherwise - int check = getSetting("mqttScCheck", MQTT_SECURE_CLIENT_CHECK).toInt(); - - if (check == SECURE_CLIENT_CHECK_FINGERPRINT) { - DEBUG_MSG_P(PSTR("[MQTT] Using fingerprint verification, trying to connect\n")); - char fp[60] = {0}; - if (sslFingerPrintChar(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) { - - // BearSSL needs to have the fingerprint set prior to connecting - #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL - _mqtt_client_secure.setFingerprint(fp); // Always returns true - #endif - - if (_mqtt_client_secure.connect(_mqtt_server.c_str(), _mqtt_port)) { - // AxTLS does the fingerprint check *after* connecting - #if SECURE_CLIENT == SECURE_CLIENT_AXTLS - if (!_mqtt_client_secure.verify(fp, _mqtt_server.c_str())) { - DEBUG_MSG_P(PSTR("[MQTT] Fingerprint did not match\n")); - verified = false; - } - #endif - } else { - DEBUG_MSG_P(PSTR("[MQTT] Client connection failed, incorrect fingerprint?\n")); - verified = false; - } - } else { - DEBUG_MSG_P(PSTR("[MQTT] Invalid fingerprint, cannot connect\n")); - verified = false; - } - } else if (check == SECURE_CLIENT_CHECK_CA) { - #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL - #ifndef _mqtt_client_ca - DEBUG_MSG_P(PSTR("[MQTT] No CA certificate defined, cannot connect\n")); - verified = false; - #else - DEBUG_MSG_P(PSTR("[MQTT] Using CA verification, trying to connect\n")); - - // We need to allocate using new in order to keep the list in memory - _ca_list = new BearSSL::X509List(_mqtt_client_ca); - - // If NTP is not synced yet, the connect() call may fail. - // This is not an issue, MQTT will reconnect after MQTT_RECONNECT_DELAY_MIN - #if NTP_SUPPORT - _mqtt_client_secure.setX509Time(ntpLocal2UTC(now())); - #else - _mqtt_client_secure.setX509Time(now()); - #endif - - _mqtt_client_secure.setTrustAnchors(_ca_list); - - if (!_mqtt_client_secure.connect(_mqtt_server.c_str(), _mqtt_port)) { - DEBUG_MSG_P(PSTR("[MQTT] CA verification failed - possible reasons are an incorrect certificate or unsynced clock\n")); - verified = false; - } - #endif // defined _mqtt_client_ca - #else - DEBUG_MSG_P(PSTR("[MQTT] CA verification is not supported with AxTLS client, cannot connect\n")); - verified = false; - #endif - } else { - DEBUG_MSG_P(PSTR("[MQTT] !!! SSL connection will not be validated !!!\n")); - - #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL - _mqtt_client_secure.setInsecure(); - #endif - } - } - - #endif // SECURE_CLIENT != SECURE_CLIENT_NONE - - if (verified) { - #if MQTT_LIBRARY == MQTT_ARDUINO // Arduino-MQTT - _mqtt.begin(_mqtt_server.c_str(), _mqtt_port, (secure ? _mqtt_client_secure : _mqtt_client)); - _mqtt.setWill(_mqtt_will.c_str(), MQTT_STATUS_OFFLINE, _mqtt_qos, _mqtt_retain); - verified = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_user.c_str(), _mqtt_pass.c_str()); - #else // PubSubClient - _mqtt.setClient(secure ? _mqtt_client_secure : _mqtt_client); - _mqtt.setServer(_mqtt_server.c_str(), _mqtt_port); - - if (_mqtt_user.length() && _mqtt_pass.length()) { - DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user.c_str()); - verified = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_user.c_str(), _mqtt_pass.c_str(), _mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, MQTT_STATUS_OFFLINE); - } else { - verified = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, MQTT_STATUS_OFFLINE); - } - #endif - } + #if SECURE_CLIENT != SECURE_CLIENT_NONE + const bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1; + #else + const bool secure = false; + #endif - if (verified) { + #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT + _mqttSetupAsyncClient(secure); + #elif (MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT) || (MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT) + if (_mqttSetupSyncClient(secure) && _mqttConnectSyncClient(secure)) { _mqttOnConnect(); } else { DEBUG_MSG_P(PSTR("[MQTT] Connection failed\n")); - mqttDisconnect(); // Clean up - _mqtt_last_connection = millis(); + _mqttOnDisconnect(); } - - #endif // MQTT_LIBRARY == MQTT_ASYNC + #else + #error "please check that MQTT_LIBRARY is valid" + #endif } @@ -399,11 +370,29 @@ void _mqttBackwards() { } void _mqttInfo() { - DEBUG_MSG_P(PSTR("[MQTT] Library %s, SSL %s, Autoconnect %s\n"), - (MQTT_LIBRARY == MQTT_ASYNC ? "AsyncMqttClient" : (MQTT_LIBRARY == MQTT_ARDUINO ? "Arduino-MQTT" : "PubSubClient")), - SECURE_CLIENT == SECURE_CLIENT_NONE ? "DISABLED" : "ENABLED", - MQTT_AUTOCONNECT ? "ENABLED" : "DISABLED" - ); + DEBUG_MSG_P(PSTR( + "[MQTT] " + #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT + "AsyncMqttClient" + #elif MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT + "Arduino-MQTT" + #elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT + "PubSubClient" + #endif + ", SSL " + #if SECURE_CLIENT != SEURE_CLIENT_NONE + "ENABLED" + #else + "DISABLED" + #endif + ", Autoconnect " + #if MQTT_AUTOCONNECT + "ENABLED" + #else + "DISABLED" + #endif + "\n" + )); DEBUG_MSG_P(PSTR("[MQTT] Client %s, %s\n"), _mqtt_enabled ? "ENABLED" : "DISABLED", _mqtt.connected() ? "CONNECTED" : "DISCONNECTED" @@ -520,6 +509,7 @@ void _mqttOnConnect() { _mqtt_last_connection = millis(); _mqtt_connecting = false; + _mqtt_connected = true; // Clean subscriptions mqttUnsubscribeRaw("#"); @@ -536,6 +526,7 @@ void _mqttOnDisconnect() { // Reset reconnection delay _mqtt_last_connection = millis(); _mqtt_connecting = false; + _mqtt_connected = false; DEBUG_MSG_P(PSTR("[MQTT] Disconnected!\n")); @@ -633,13 +624,13 @@ String mqttTopic(const char * magnitude, unsigned int index, bool is_set) { void mqttSendRaw(const char * topic, const char * message, bool retain) { if (_mqtt.connected()) { - #if MQTT_LIBRARY == MQTT_ASYNC // AsyncMqttClient + #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT unsigned int packetId = _mqtt.publish(topic, _mqtt_qos, retain, message); DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s (PID %d)\n"), topic, message, packetId); - #elif MQTT_LIBRARY == MQTT_ARDUINO // Arduino-MQTT + #elif MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT _mqtt.publish(topic, message, retain, _mqtt_qos); DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message); - #else // PubSubClient + #elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT _mqtt.publish(topic, message, retain); DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message); #endif @@ -804,7 +795,7 @@ int8_t mqttEnqueue(const char * topic, const char * message) { void mqttSubscribeRaw(const char * topic) { if (_mqtt.connected() && (strlen(topic) > 0)) { - #if MQTT_LIBRARY == MQTT_ASYNC // AsyncMqttClient + #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT unsigned int packetId = _mqtt.subscribe(topic, _mqtt_qos); DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s (PID %d)\n"), topic, packetId); #else // Arduino-MQTT or PubSubClient @@ -820,7 +811,7 @@ void mqttSubscribe(const char * topic) { void mqttUnsubscribeRaw(const char * topic) { if (_mqtt.connected() && (strlen(topic) > 0)) { - #if MQTT_LIBRARY == MQTT_ASYNC // AsyncMqttClient + #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT unsigned int packetId = _mqtt.unsubscribe(topic); DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing to %s (PID %d)\n"), topic, packetId); #else // Arduino-MQTT or PubSubClient @@ -853,9 +844,6 @@ void mqttDisconnect() { DEBUG_MSG_P(PSTR("[MQTT] Disconnecting\n")); _mqtt.disconnect(); } - #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL - delete _ca_list; - #endif } bool mqttForward() { @@ -889,7 +877,20 @@ void mqttSetup() { _mqttBackwards(); _mqttInfo(); - #if MQTT_LIBRARY == MQTT_ASYNC // AsyncMqttClient + #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT + + // XXX: should not place this in config, addServerFingerprint does not check for duplicates + #if SECURE_CLIENT != SECURE_CLIENT_NONE + { + if (_mqtt_sc_config.on_fingerprint) { + const String fingerprint = _mqtt_sc_config.on_fingerprint(); + uint8_t buffer[20] = {0}; + if (sslFingerPrintArray(fingerprint.c_str(), buffer)) { + _mqtt.addServerFingerprint(buffer); + } + } + } + #endif // SECURE_CLIENT != SECURE_CLIENT_NONE _mqtt.onConnect([](bool sessionPresent) { _mqttOnConnect(); @@ -927,19 +928,19 @@ void mqttSetup() { DEBUG_MSG_P(PSTR("[MQTT] Publish ACK for PID %d\n"), packetId); }); - #elif MQTT_LIBRARY == MQTT_ARDUINO // Arduino-MQTT + #elif MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT _mqtt.onMessageAdvanced([](MQTTClient *client, char topic[], char payload[], int length) { _mqttOnMessage(topic, payload, length); }); - #else // PubSubClient + #elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT _mqtt.setCallback([](char* topic, byte* payload, unsigned int length) { _mqttOnMessage(topic, (char *) payload, length); }); - #endif // MQTT_LIBRARY == MQTT_ASYNC + #endif // MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT _mqttConfigure(); mqttRegister(_mqttCallback); @@ -970,11 +971,11 @@ void mqttLoop() { if (WiFi.status() != WL_CONNECTED) return; - #if MQTT_LIBRARY == MQTT_ASYNC + #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT _mqttConnect(); - #else // MQTT_LIBRARY != MQTT_ASYNC + #else // MQTT_LIBRARY != MQTT_LIBRARY_ASYNCMQTTCLIENT if (_mqtt.connected()) { @@ -984,14 +985,13 @@ void mqttLoop() { if (_mqtt_connected) { _mqttOnDisconnect(); - _mqtt_connected = false; } _mqttConnect(); } - #endif // MQTT_LIBRARY == MQTT_ASYNC + #endif // MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT } diff --git a/code/espurna/ota_httpupdate.ino b/code/espurna/ota_httpupdate.ino index 7cc24d80..53ffee27 100644 --- a/code/espurna/ota_httpupdate.ino +++ b/code/espurna/ota_httpupdate.ino @@ -19,6 +19,18 @@ Copyright (C) 2019 by Maxim Prokhorov #include "libs/URL.h" +#if SECURE_CLIENT != SECURE_CLIENT_NONE + + #if OTA_SECURE_CLIENT_INCLUDE_CA + #include "static/ota_client_trusted_root_ca.h" + #else + #include "static/digicert_evroot_pem.h" + #define _ota_client_trusted_root_ca _ssl_digicert_ev_root_ca + #endif + +#endif // SECURE_CLIENT != SECURE_CLIENT_NONE + + void _otaClientRunUpdater(WiFiClient* client, const String& url, const String& fp = "") { UNUSED(client); @@ -113,7 +125,7 @@ void _otaClientFromHttps(const String& url) { BearSSL::X509List *ca = nullptr; if (check == SECURE_CLIENT_CHECK_CA) { - ca = new BearSSL::X509List(_ota_client_http_update_ca); + ca = new BearSSL::X509List(_ota_client_trusted_root_ca); // because we do not support libc methods of getting time, force client to use ntpclientlib's current time // XXX: local2utc method use is detrimental when DST happening. now() should be utc client->setX509Time(ntpLocal2UTC(now()));