Browse Source

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
master
Max Prokhorov 5 years ago
committed by GitHub
parent
commit
e398685ba4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 482 additions and 237 deletions
  1. +26
    -17
      code/espurna/config/general.h
  2. +10
    -17
      code/espurna/config/prototypes.h
  3. +4
    -3
      code/espurna/config/types.h
  4. +230
    -0
      code/espurna/libs/SecureClientHelpers.h
  5. +199
    -199
      code/espurna/mqtt.ino
  6. +13
    -1
      code/espurna/ota_httpupdate.ino

+ 26
- 17
code/espurna/config/general.h View File

@ -789,8 +789,8 @@
// TODO: eventually should be replaced with pre-parsed structs, read directly from flash // TODO: eventually should be replaced with pre-parsed structs, read directly from flash
// (ref: https://github.com/earlephilhower/bearssl-esp8266/pull/14) // (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) // By default, using DigiCert root in "static/digicert_evroot_pem.h" (for https://github.com)
#endif #endif
@ -859,27 +859,36 @@
#ifndef MQTT_LIBRARY #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 #endif
// -----------------------------------------------------------------------------
// MQTT OVER SSL // 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 // 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 // $ 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 #ifndef MQTT_SSL_ENABLED
#define MQTT_SSL_ENABLED 0 // By default MQTT over SSL will not be 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 #ifndef MQTT_SECURE_CLIENT_INCLUDE_CA
#define MQTT_SECURE_CLIENT_INCLUDE_CA 0 // Use user-provided CA. Only PROGMEM PEM option is supported. #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" // By default, using LetsEncrypt X3 root in "static/letsencrypt_isrgroot_pem.h"
#endif #endif


+ 10
- 17
code/espurna/config/prototypes.h View File

@ -183,6 +183,15 @@ void lightChannel(unsigned char id, unsigned char value);
// MQTT // MQTT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
#include <ESPAsyncTCP.h>
#include <AsyncMqttClient.h>
#elif MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT
#include <MQTTClient.h>
#elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT
#include <PubSubClient.h>
#endif
using mqtt_callback_f = std::function<void(unsigned int, const char *, char *)>; using mqtt_callback_f = std::function<void(unsigned int, const char *, char *)>;
#if MQTT_SUPPORT #if MQTT_SUPPORT
@ -190,13 +199,6 @@ using mqtt_callback_f = std::function<void(unsigned int, const char *, char *)>;
String mqttMagnitude(char * topic); String mqttMagnitude(char * topic);
#endif #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 // OTA
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -213,17 +215,8 @@ using mqtt_callback_f = std::function<void(unsigned int, const char *, char *)>;
#endif #endif
#if SECURE_CLIENT != SECURE_CLIENT_NONE #if SECURE_CLIENT != SECURE_CLIENT_NONE
#include <WiFiClientSecure.h> #include <WiFiClientSecure.h>
#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 // RFM69


+ 4
- 3
code/espurna/config/types.h View File

@ -152,9 +152,10 @@
#define MQTT_DISCONNECT_EVENT 1 #define MQTT_DISCONNECT_EVENT 1
#define MQTT_MESSAGE_EVENT 2 #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
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------


+ 230
- 0
code/espurna/libs/SecureClientHelpers.h View File

@ -0,0 +1,230 @@
// -----------------------------------------------------------------------------
// WiFiClientSecure validation helpers
// -----------------------------------------------------------------------------
#pragma once
#if SECURE_CLIENT != SECURE_CLIENT_NONE
#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
#include <WiFiClientSecureBearSSL.h>
#elif SECURE_CLIENT == SECURE_CLIENT_AXTLS
#include <WiFiClientSecureAxTLS.h>
#endif
namespace SecureClientHelpers {
using host_callback_f = std::function<String()>;
using check_callback_f = std::function<bool()>;
using fp_callback_f = std::function<String()>;
using cert_callback_f = std::function<const char*()>;
using mfln_callback_f = std::function<uint16_t()>;
#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<SecureClientClass>())
{}
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<SecureClientClass> _client;
};
};
using SecureClientConfig = SecureClientHelpers::SecureClientConfig;
using SecureClientChecks = SecureClientHelpers::SecureClientChecks;
using SecureClient = SecureClientHelpers::SecureClient;
#endif // SECURE_CLIENT != SECURE_CLIENT_NONE

+ 199
- 199
code/espurna/mqtt.ino View File

@ -3,6 +3,7 @@
MQTT MODULE MQTT MODULE
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com> Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
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 <xose dot perez at gmail dot com>
#include <Ticker.h> #include <Ticker.h>
#include <TimeLib.h> #include <TimeLib.h>
#if MQTT_LIBRARY == MQTT_ASYNC // AsyncMqttClient
#include <AsyncMqttClient.h>
#include "libs/SecureClientHelpers.h"
#if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
AsyncMqttClient _mqtt; AsyncMqttClient _mqtt;
#else // Arduino-MQTT or PubSubClient
#else // MQTT_LIBRARY_ARDUINOMQTT / MQTT_LIBRARY_PUBSUBCLIENT
WiFiClient _mqtt_client; WiFiClient _mqtt_client;
bool _mqtt_connected = false;
#include "WiFiClientSecure.h"
#if SECURE_CLIENT != SECURE_CLIENT_NONE
std::unique_ptr<SecureClient> _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 <MQTTClient.h>
#ifdef MQTT_MAX_PACKET_SIZE
#if MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT
#ifdef MQTT_MAX_PACKET_SIZE
MQTTClient _mqtt(MQTT_MAX_PACKET_SIZE); MQTTClient _mqtt(MQTT_MAX_PACKET_SIZE);
#else
MQTTClient _mqtt();
#endif
#else // Using PubSubClient
#include <PubSubClient.h>
#else
MQTTClient _mqtt;
#endif
#elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT
PubSubClient _mqtt; PubSubClient _mqtt;
#endif
#endif #endif
#endif // MQTT_LIBRARY == MQTT_ASYNCMQTTCLIENT
bool _mqtt_enabled = MQTT_ENABLED; bool _mqtt_enabled = MQTT_ENABLED;
bool _mqtt_use_json = false; bool _mqtt_use_json = false;
unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN; unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
unsigned long _mqtt_last_connection = 0; unsigned long _mqtt_last_connection = 0;
bool _mqtt_connected = false;
bool _mqtt_connecting = false; bool _mqtt_connecting = false;
unsigned char _mqtt_qos = MQTT_QOS; unsigned char _mqtt_qos = MQTT_QOS;
bool _mqtt_retain = MQTT_RETAIN; bool _mqtt_retain = MQTT_RETAIN;
@ -83,6 +89,115 @@ Ticker _mqtt_flush_ticker;
// Private // 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<SecureClient>(_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() { void _mqttConnect() {
// Do not connect if disabled // Do not connect if disabled
@ -114,168 +229,24 @@ void _mqttConnect() {
_mqtt_connecting = true; _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(); _mqttOnConnect();
} else { } else {
DEBUG_MSG_P(PSTR("[MQTT] Connection failed\n")); 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() { 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"), DEBUG_MSG_P(PSTR("[MQTT] Client %s, %s\n"),
_mqtt_enabled ? "ENABLED" : "DISABLED", _mqtt_enabled ? "ENABLED" : "DISABLED",
_mqtt.connected() ? "CONNECTED" : "DISCONNECTED" _mqtt.connected() ? "CONNECTED" : "DISCONNECTED"
@ -520,6 +509,7 @@ void _mqttOnConnect() {
_mqtt_last_connection = millis(); _mqtt_last_connection = millis();
_mqtt_connecting = false; _mqtt_connecting = false;
_mqtt_connected = true;
// Clean subscriptions // Clean subscriptions
mqttUnsubscribeRaw("#"); mqttUnsubscribeRaw("#");
@ -536,6 +526,7 @@ void _mqttOnDisconnect() {
// Reset reconnection delay // Reset reconnection delay
_mqtt_last_connection = millis(); _mqtt_last_connection = millis();
_mqtt_connecting = false; _mqtt_connecting = false;
_mqtt_connected = false;
DEBUG_MSG_P(PSTR("[MQTT] Disconnected!\n")); 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) { void mqttSendRaw(const char * topic, const char * message, bool retain) {
if (_mqtt.connected()) { 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); unsigned int packetId = _mqtt.publish(topic, _mqtt_qos, retain, message);
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s (PID %d)\n"), topic, message, packetId); 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); _mqtt.publish(topic, message, retain, _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message); DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message);
#else // PubSubClient
#elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT
_mqtt.publish(topic, message, retain); _mqtt.publish(topic, message, retain);
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message); DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message);
#endif #endif
@ -804,7 +795,7 @@ int8_t mqttEnqueue(const char * topic, const char * message) {
void mqttSubscribeRaw(const char * topic) { void mqttSubscribeRaw(const char * topic) {
if (_mqtt.connected() && (strlen(topic) > 0)) { 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); unsigned int packetId = _mqtt.subscribe(topic, _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s (PID %d)\n"), topic, packetId); DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s (PID %d)\n"), topic, packetId);
#else // Arduino-MQTT or PubSubClient #else // Arduino-MQTT or PubSubClient
@ -820,7 +811,7 @@ void mqttSubscribe(const char * topic) {
void mqttUnsubscribeRaw(const char * topic) { void mqttUnsubscribeRaw(const char * topic) {
if (_mqtt.connected() && (strlen(topic) > 0)) { if (_mqtt.connected() && (strlen(topic) > 0)) {
#if MQTT_LIBRARY == MQTT_ASYNC // AsyncMqttClient
#if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
unsigned int packetId = _mqtt.unsubscribe(topic); unsigned int packetId = _mqtt.unsubscribe(topic);
DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing to %s (PID %d)\n"), topic, packetId); DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing to %s (PID %d)\n"), topic, packetId);
#else // Arduino-MQTT or PubSubClient #else // Arduino-MQTT or PubSubClient
@ -853,9 +844,6 @@ void mqttDisconnect() {
DEBUG_MSG_P(PSTR("[MQTT] Disconnecting\n")); DEBUG_MSG_P(PSTR("[MQTT] Disconnecting\n"));
_mqtt.disconnect(); _mqtt.disconnect();
} }
#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
delete _ca_list;
#endif
} }
bool mqttForward() { bool mqttForward() {
@ -889,7 +877,20 @@ void mqttSetup() {
_mqttBackwards(); _mqttBackwards();
_mqttInfo(); _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) { _mqtt.onConnect([](bool sessionPresent) {
_mqttOnConnect(); _mqttOnConnect();
@ -927,19 +928,19 @@ void mqttSetup() {
DEBUG_MSG_P(PSTR("[MQTT] Publish ACK for PID %d\n"), packetId); 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) { _mqtt.onMessageAdvanced([](MQTTClient *client, char topic[], char payload[], int length) {
_mqttOnMessage(topic, payload, length); _mqttOnMessage(topic, payload, length);
}); });
#else // PubSubClient
#elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT
_mqtt.setCallback([](char* topic, byte* payload, unsigned int length) { _mqtt.setCallback([](char* topic, byte* payload, unsigned int length) {
_mqttOnMessage(topic, (char *) payload, length); _mqttOnMessage(topic, (char *) payload, length);
}); });
#endif // MQTT_LIBRARY == MQTT_ASYNC
#endif // MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
_mqttConfigure(); _mqttConfigure();
mqttRegister(_mqttCallback); mqttRegister(_mqttCallback);
@ -970,11 +971,11 @@ void mqttLoop() {
if (WiFi.status() != WL_CONNECTED) return; if (WiFi.status() != WL_CONNECTED) return;
#if MQTT_LIBRARY == MQTT_ASYNC
#if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
_mqttConnect(); _mqttConnect();
#else // MQTT_LIBRARY != MQTT_ASYNC
#else // MQTT_LIBRARY != MQTT_LIBRARY_ASYNCMQTTCLIENT
if (_mqtt.connected()) { if (_mqtt.connected()) {
@ -984,14 +985,13 @@ void mqttLoop() {
if (_mqtt_connected) { if (_mqtt_connected) {
_mqttOnDisconnect(); _mqttOnDisconnect();
_mqtt_connected = false;
} }
_mqttConnect(); _mqttConnect();
} }
#endif // MQTT_LIBRARY == MQTT_ASYNC
#endif // MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
} }


+ 13
- 1
code/espurna/ota_httpupdate.ino View File

@ -19,6 +19,18 @@ Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
#include "libs/URL.h" #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 = "") { void _otaClientRunUpdater(WiFiClient* client, const String& url, const String& fp = "") {
UNUSED(client); UNUSED(client);
@ -113,7 +125,7 @@ void _otaClientFromHttps(const String& url) {
BearSSL::X509List *ca = nullptr; BearSSL::X509List *ca = nullptr;
if (check == SECURE_CLIENT_CHECK_CA) { 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 // 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 // XXX: local2utc method use is detrimental when DST happening. now() should be utc
client->setX509Time(ntpLocal2UTC(now())); client->setX509Time(ntpLocal2UTC(now()));


Loading…
Cancel
Save