Merge pull request #13887 from s-hadinger/tls_dual_mode

MQTT TLS dual mode (CA or fingeprint) in same firmware, ``SetOption132 1`` to force fingerprint
This commit is contained in:
s-hadinger 2021-12-01 21:54:37 +01:00 committed by GitHub
commit 40087595ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 73 additions and 95 deletions

View File

@ -14,7 +14,6 @@ Note: `minimal` variant is not listed as it shouldn't be used outside of the [up
| USE_DOMOTICZ | - | x / x | x | x | x | - |
| USE_HOME_ASSISTANT | - | x / x | x | x | x | - |
| USE_MQTT_TLS | - | - / - | - | - | - | - |
| USE_MQTT_TLS_CA_CERT | - | - / - | - | - | - | - |
| USE_MQTT_AWS_IOT | - | - / - | - | - | - | - |
| USE_4K_RSA | - | - / - | - | - | - | - |
| USE_TELEGRAM | - | - / - | - | - | - | - |

View File

@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
### Changed
- (Internal) Range conversion edge values
- NimBLE to v.1.3.3
- MQTT TLS dual mode (CA or fingeprint) in same firmware, ``SetOption132 1`` to force fingerprint
### Fixed
- Tuya dimmer range issue (#13849)

View File

@ -43,10 +43,8 @@ uint32_t stack_thunk_light_refcnt = 0;
//#define _stackSize (5600/4)
#if defined(USE_MQTT_AWS_IOT) || defined(USE_MQTT_AWS_IOT_LIGHT) || defined(USE_MQTT_AZURE_IOT)
#define _stackSize (5300/4) // using a light version of bearssl we can save 300 bytes
#elif defined(USE_MQTT_TLS_FORCE_EC_CIPHER) || defined(USE_4K_RSA)
#define _stackSize (4800/4) // no private key, we can reduce a little, max observed 4300
#else
#define _stackSize (3800/4) // using a light version of bearssl we can save 2k
#define _stackSize (4800/4) // no private key, we can reduce a little, max observed 4300
#endif
#define _stackPaint 0xdeadbeef

View File

@ -191,11 +191,7 @@ void WiFiClientSecure_light::_clear() {
_last_error = 0;
_recvapp_buf = nullptr;
_recvapp_len = 0;
#ifdef USE_MQTT_TLS_CA_CERT
_insecure = false; // insecure (fingerprint) mode is only enabled if setPubKeyFingerprint() is called
#else
_insecure = true; // force insecure if CA validation is not enabled
#endif
_insecure = false; // set to true when calling setPubKeyFingerprint()
_fingerprint_any = true; // by default accept all fingerprints
_fingerprint1 = nullptr;
_fingerprint2 = nullptr;
@ -920,11 +916,7 @@ extern "C" {
// We limit to a single cipher to reduce footprint
// we reference it, don't put in PROGMEM
static const uint16_t suites[] = {
#ifdef USE_MQTT_TLS_FORCE_EC_CIPHER
BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
#else
BR_TLS_RSA_WITH_AES_128_GCM_SHA256
#endif
};
// Default initializion for our SSL clients
@ -947,10 +939,8 @@ extern "C" {
br_ssl_engine_set_aes_ctr(&cc->eng, &br_aes_small_ctr_vtable);
br_ssl_engine_set_ghash(&cc->eng, &br_ghash_ctmul32);
#ifdef USE_MQTT_TLS_FORCE_EC_CIPHER
// we support only P256 EC curve for AWS IoT, no EC curve for Letsencrypt unless forced
br_ssl_engine_set_ec(&cc->eng, &br_ec_p256_m15); // TODO
#endif
}
}
@ -958,9 +948,8 @@ extern "C" {
// Returns if the SSL handshake succeeded.
bool WiFiClientSecure_light::_connectSSL(const char* hostName) {
// Validation context, either full CA validation or checking only fingerprints
#ifdef USE_MQTT_TLS_CA_CERT
br_x509_minimal_context *x509_minimal = nullptr;
#endif
br_x509_pubkeyfingerprint_context *x509_insecure = nullptr;
LOG_HEAP_SIZE("_connectSSL.start");
@ -998,7 +987,6 @@ bool WiFiClientSecure_light::_connectSSL(const char* hostName) {
br_x509_pubkeyfingerprint_init(x509_insecure, _fingerprint1, _fingerprint2, _recv_fingerprint, _fingerprint_any);
br_ssl_engine_set_x509(_eng, &x509_insecure->vtable);
#ifdef USE_MQTT_TLS_CA_CERT
if (!_insecure) {
x509_minimal = (br_x509_minimal_context*) malloc(sizeof(br_x509_minimal_context));
if (!x509_minimal) break;
@ -1011,7 +999,6 @@ bool WiFiClientSecure_light::_connectSSL(const char* hostName) {
if (cfg_time > now) { now = cfg_time; }
br_x509_minimal_set_time(x509_minimal, now / 86400 + 719528, now % 86400);
}
#endif
LOG_HEAP_SIZE("_connectSSL after DecoderContext allocation");
// ============================================================
@ -1050,9 +1037,7 @@ bool WiFiClientSecure_light::_connectSSL(const char* hostName) {
LOG_HEAP_SIZE("_connectSSL.end, freeing StackThunk");
#endif // ESP8266
#ifdef USE_MQTT_TLS_CA_CERT
free(x509_minimal);
#endif
free(x509_minimal); // safe to call if nullptr
free(x509_insecure);
LOG_HEAP_SIZE("_connectSSL after release of Priv Key");
return ret;
@ -1065,9 +1050,7 @@ bool WiFiClientSecure_light::_connectSSL(const char* hostName) {
#ifdef ESP8266
stack_thunk_light_del_ref();
#endif
#ifdef USE_MQTT_TLS_CA_CERT
free(x509_minimal);
#endif
free(x509_minimal); // safe to call if nullptr
free(x509_insecure);
LOG_HEAP_SIZE("_connectSSL clean_on_error");
return false;

View File

@ -371,6 +371,7 @@
// Commands xdrv_02_mqtt.ino
#define D_SO_MQTTJSONONLY "MqttJSONOnly"
#define D_SO_MQTTTLS "MqttTLS"
#define D_SO_MQTTTLS_FINGERPRINT "MqttTLSFingerprint"
#define D_SO_MQTTNORETAIN "MqttNoRetain"
#define D_SO_MQTTDETACHRELAY "MqttDetachRelay"
#define D_CMND_MQTTLOG "MqttLog"

View File

@ -170,6 +170,7 @@
#define MQTT_INDEX_SEPARATOR false // [SetOption64] Enable "_" instead of "-" as sensor index separator
#define MQTT_TUYA_RECEIVED false // [SetOption66] Enable TuyaMcuReceived messages over Mqtt
#define MQTT_TLS_ENABLED false // [SetOption103] Enable TLS mode (requires TLS version)
#define MQTT_TLS_FINGERPRINT false // [SetOption132] Force TLS fingerprint validation instead of CA (requires TLS version)
// -- HTTP ----------------------------------------
#define WEB_SERVER 2 // [WebServer] Web server (0 = Off, 1 = Start as User, 2 = Start as Admin)
@ -430,9 +431,7 @@
// -- MQTT - TLS - AWS IoT ------------------------
// Using TLS starting with version v6.5.0.16 compilation will only work using Core 2.4.2 and 2.5.2. No longer supported: 2.3.0
//#define USE_MQTT_TLS // Use TLS for MQTT connection (+34.5k code, +7.0k mem and +4.8k additional during connection handshake)
// #define USE_MQTT_TLS_CA_CERT // Force full CA validation instead of fingerprints, slower, but simpler to use. (+2.2k code, +1.9k mem during connection handshake)
// This includes the LetsEncrypt CA in tasmota_ca.ino for verifying server certificates
// #define USE_MQTT_TLS_FORCE_EC_CIPHER // Force Elliptic Curve cipher (higher security) required by some servers (automatically enabled with USE_MQTT_AWS_IOT) (+11.4k code, +0.4k mem)
// #define USE_MQTT_TLS_CA_CERT // [DEPRECATED] Now TLS supports dual mode using SetOption132 - this flag is now ignored
// #define USE_MQTT_AWS_IOT_LIGHT // Enable MQTT for AWS IoT in light mode, with user/password instead of private certificate
// #define USE_MQTT_AWS_IOT // [Deprecated] Enable MQTT for AWS IoT - requires a private key (+11.9k code, +0.4k mem)
// Note: you need to generate a private key + certificate per device and update 'tasmota/tasmota_aws_iot.cpp'
@ -453,7 +452,6 @@
// -- Telegram Protocol ---------------------------
//#define USE_TELEGRAM // Support for Telegram protocol (+49k code, +7.0k mem and +4.8k additional during connection handshake)
#define USE_TELEGRAM_FINGERPRINT "\xB2\x72\x47\xA6\x69\x8C\x3C\x69\xF9\x58\x6C\xF3\x60\x02\xFB\x83\xFA\x8B\x1F\x23" // Telegram api.telegram.org TLS public key fingerpring
// #define USE_MQTT_TLS_CA_CERT // Use certificate instead of fingerprint
// -- KNX IP Protocol -----------------------------
//#define USE_KNX // Enable KNX IP Protocol Support (+9.4k code, +3k7 mem)
@ -1117,10 +1115,6 @@
#if defined(USE_MQTT_TLS) || defined(USE_SENDMAIL) || defined(USE_TELEGRAM) || defined(USE_WEBCLIENT_HTTPS) || defined(USE_ALEXA_AVS)
#define USE_TLS // flag indicates we need to include TLS code
#if defined(USE_MQTT_AWS_IOT) || defined(USE_TELEGRAM) || defined(USE_WEBCLIENT_HTTPS)
#define USE_MQTT_TLS_FORCE_EC_CIPHER // AWS IoT and TELEGRAM require EC Cipher
#endif
#endif
#endif // _MY_USER_CONFIG_H_

View File

@ -161,7 +161,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t energy_phase : 1; // bit 15 (v9.5.0.9) - SetOption129 - (Energy) Show phase information
uint32_t show_heap_with_timestamp : 1; // bit 16 (v9.5.0.9) - SetOption130 - (Debug) Show heap with logging timestamp
uint32_t tuya_allow_dimmer_0 : 1; // bit 17 (v10.0.0.3) - SetOption131 - (Tuya) Allow save dimmer = 0 receved by MCU
uint32_t spare18 : 1; // bit 18
uint32_t tls_use_fingerprint : 1; // bit 18 (v10.0.0.4) - SetOption132 - (TLS) use fingerprint validation instead of CA based
uint32_t spare19 : 1; // bit 19
uint32_t spare20 : 1; // bit 20
uint32_t spare21 : 1; // bit 21

View File

@ -1458,6 +1458,14 @@ void SettingsDelta(void) {
if (Settings->version < 0x0A000003) {
if (0 == Settings->param[P_ARP_GRATUITOUS]) {
Settings->param[P_ARP_GRATUITOUS] = WIFI_ARP_INTERVAL;
#ifdef USE_TLS
for (uint32_t i = 0; i < 20; i++) {
if (Settings->mqtt_fingerprint[0][i]) {
Settings->flag5.tls_use_fingerprint = true; // if the fingerprint1 is non null we expect it to be actually used
break;
}
}
#endif
}
}

View File

@ -1068,6 +1068,9 @@ void CmndSetoptionBase(bool indexed) {
TasmotaGlobal.restart_flag = 2;
}
break;
case 18: // SetOption132 - TLS Fingerprint
TasmotaGlobal.restart_flag = 2;
break;
}
}
} else {

View File

@ -21,7 +21,7 @@
// Please use fingerprint validation instead
// However, the CA are available below for future use if it appears to be useful
#if defined(USE_TLS) && defined(USE_MQTT_TLS_CA_CERT)
#if defined(USE_TLS)
/*********************************************************************************************\
* LetsEncrypt R3 certificate, RSA 2048 bits SHA 256, valid until 20250915
@ -232,4 +232,4 @@ const br_x509_trust_anchor GoDaddyCAG2_TA PROGMEM = {
}
};
#endif // defined(USE_TLS) && defined(USE_MQTT_TLS_CA_CERT)
#endif // defined(USE_TLS)

View File

@ -497,9 +497,6 @@
// -- MQTT - TLS - AWS IoT ------------------------
#ifdef USE_ZBBRIDGE_TLS // Enable TLS for ZbBridge
#define USE_MQTT_TLS // Use TLS for MQTT connection (+34.5k code, +7.0k mem and +4.8k additional during connection handshake)
#define USE_MQTT_TLS_CA_CERT // Force full CA validation instead of fingerprints, slower, but simpler to use. (+2.2k code, +1.9k mem during connection handshake)
// This includes the LetsEncrypt CA in tasmota_ca.ino for verifying server certificates
#define USE_MQTT_TLS_FORCE_EC_CIPHER // Force Elliptic Curve cipher (higher security) required by some servers (automatically enabled with USE_MQTT_AWS_IOT) (+11.4k code, +0.4k mem)
#define USE_MQTT_AWS_IOT_LIGHT // Enable MQTT for AWS IoT in light mode, with user/password instead of private certificate
#define USE_TLS // flag indicates we need to include TLS code
#endif // USE_ZBBRIDGE_TLS

View File

@ -48,11 +48,11 @@ const char kMqttCommands[] PROGMEM = "|" // No prefix
// SetOption synonyms
D_SO_MQTTJSONONLY "|"
#ifdef USE_MQTT_TLS
D_SO_MQTTTLS "|"
D_SO_MQTTTLS "|" D_SO_MQTTTLS_FINGERPRINT "|"
#endif
D_SO_MQTTNORETAIN "|" D_SO_MQTTDETACHRELAY "|"
// regular commands
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
#if defined(USE_MQTT_TLS)
D_CMND_MQTTFINGERPRINT "|"
#endif
D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|" D_CMND_MQTTKEEPALIVE "|" D_CMND_MQTTTIMEOUT "|" D_CMND_MQTTWIFITIMEOUT "|"
@ -70,18 +70,13 @@ const char kMqttCommands[] PROGMEM = "|" // No prefix
SO_SYNONYMS(kMqttSynonyms,
90,
#ifdef USE_MQTT_TLS
103,
103, 132,
#endif
104, 114
);
// const uint8_t kMqttSynonyms[] PROGMEM = {
// 4, // number of synonyms
// 90, 103, 104, 114,
// };
void (* const MqttCommand[])(void) PROGMEM = {
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
#if defined(USE_MQTT_TLS)
&CmndMqttFingerprint,
#endif
&CmndMqttUser, &CmndMqttPassword, &CmndMqttKeepAlive, &CmndMqttTimeout, &CmndMqttWifiTimeout,
@ -231,9 +226,9 @@ void MqttInit(void) {
}
#endif
#ifdef USE_MQTT_TLS_CA_CERT
tlsClient->setTrustAnchor(Tasmota_TA, nitems(Tasmota_TA));
#endif // USE_MQTT_TLS_CA_CERT
if (!Settings->flag5.tls_use_fingerprint) {
tlsClient->setTrustAnchor(Tasmota_TA, nitems(Tasmota_TA));
}
MqttClient.setClient(*tlsClient);
} else {
@ -953,7 +948,7 @@ void MqttConnected(void) {
void MqttReconnect(void) {
char stopic[TOPSZ];
Mqtt.allowed = Settings->flag.mqtt_enabled; // SetOption3 - Enable MQTT
Mqtt.allowed = Settings->flag.mqtt_enabled && (TasmotaGlobal.restart_flag == 0); // SetOption3 - Enable MQTT, and don't connect if restart in process
if (Mqtt.allowed) {
#if defined(USE_MQTT_AZURE_DPS_SCOPEID) && defined(USE_MQTT_AZURE_DPS_PRESHAREDKEY)
ProvisionAzureDPS();
@ -1043,11 +1038,11 @@ void MqttReconnect(void) {
MqttClient.setServer(SettingsText(SET_MQTT_HOST), Settings->mqtt_port);
uint32_t mqtt_connect_time = millis();
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
bool allow_all_fingerprints;
bool learn_fingerprint1;
bool learn_fingerprint2;
if (Mqtt.mqtt_tls) {
#if defined(USE_MQTT_TLS)
bool allow_all_fingerprints = false;
bool learn_fingerprint1 = false;
bool learn_fingerprint2 = false;
if (Mqtt.mqtt_tls && Settings->flag5.tls_use_fingerprint) {
allow_all_fingerprints = false;
learn_fingerprint1 = is_fingerprint_mono_value(Settings->mqtt_fingerprint[0], 0x00);
learn_fingerprint2 = is_fingerprint_mono_value(Settings->mqtt_fingerprint[1], 0x00);
@ -1094,35 +1089,37 @@ void MqttReconnect(void) {
if (!tlsClient->getMFLNStatus()) {
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "MFLN not supported by TLS server"));
}
#ifndef USE_MQTT_TLS_CA_CERT // don't bother with fingerprints if using CA validation
const uint8_t *recv_fingerprint = tlsClient->getRecvPubKeyFingerprint();
// create a printable version of the fingerprint received
char buf_fingerprint[64];
ToHex_P(recv_fingerprint, 20, buf_fingerprint, sizeof(buf_fingerprint), ' ');
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Server fingerprint: %s"), buf_fingerprint);
bool learned = false;
if (Settings->flag5.tls_use_fingerprint) { // CA validation
const uint8_t *recv_fingerprint = tlsClient->getRecvPubKeyFingerprint();
// create a printable version of the fingerprint received
char buf_fingerprint[64];
ToHex_P(recv_fingerprint, 20, buf_fingerprint, sizeof(buf_fingerprint), ' ');
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Server fingerprint: %s"), buf_fingerprint);
// If the fingerprint slot is marked for update, we'll do so.
// Otherwise, if the fingerprint slot had the magic trust-on-first-use
// value, we will save the current fingerprint there, but only if the other fingerprint slot
// *didn't* match it.
if (recv_fingerprint[20] & 0x1 || (learn_fingerprint1 && 0 != memcmp(recv_fingerprint, Settings->mqtt_fingerprint[1], 20))) {
memcpy(Settings->mqtt_fingerprint[0], recv_fingerprint, 20);
learned = true;
}
// As above, but for the other slot.
if (recv_fingerprint[20] & 0x2 || (learn_fingerprint2 && 0 != memcmp(recv_fingerprint, Settings->mqtt_fingerprint[0], 20))) {
memcpy(Settings->mqtt_fingerprint[1], recv_fingerprint, 20);
learned = true;
bool learned = false;
// If the fingerprint slot is marked for update, we'll do so.
// Otherwise, if the fingerprint slot had the magic trust-on-first-use
// value, we will save the current fingerprint there, but only if the other fingerprint slot
// *didn't* match it.
if (recv_fingerprint[20] & 0x1 || (learn_fingerprint1 && 0 != memcmp(recv_fingerprint, Settings->mqtt_fingerprint[1], 20))) {
memcpy(Settings->mqtt_fingerprint[0], recv_fingerprint, 20);
learned = true;
}
// As above, but for the other slot.
if (recv_fingerprint[20] & 0x2 || (learn_fingerprint2 && 0 != memcmp(recv_fingerprint, Settings->mqtt_fingerprint[0], 20))) {
memcpy(Settings->mqtt_fingerprint[1], recv_fingerprint, 20);
learned = true;
}
if (learned) {
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Fingerprint learned: %s"), buf_fingerprint);
SettingsSaveAll(); // save settings
}
}
if (learned) {
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Fingerprint learned: %s"), buf_fingerprint);
SettingsSaveAll(); // save settings
}
#endif // !USE_MQTT_TLS_CA_CERT
}
#endif // USE_MQTT_TLS
MqttConnected();
@ -1275,7 +1272,7 @@ bool KeyTopicActive(uint32_t key) {
* Commands
\*********************************************************************************************/
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
#if defined(USE_MQTT_TLS)
void CmndMqttFingerprint(void) {
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
char fingerprint[60];

View File

@ -47,13 +47,8 @@
#define TELEGRAM_SEND_RETRY 4 // Retries
#define TELEGRAM_MAX_MESSAGES 2
#ifdef USE_MQTT_TLS_CA_CERT
static const uint32_t tls_rx_size = 2048; // since Telegram CA is bigger than 1024 bytes, we need to increase rx buffer
static const uint32_t tls_tx_size = 1024;
#else
static const uint32_t tls_rx_size = 1024;
static const uint32_t tls_tx_size = 1024;
#endif
static const uint32_t tls_rx_size = 2048; // since Telegram CA is bigger than 1024 bytes, we need to increase rx buffer
static const uint32_t tls_tx_size = 1024;
#include "WiFiClientSecureLightBearSSL.h"
BearSSL::WiFiClientSecure_light *telegramClient = nullptr;
@ -87,11 +82,13 @@ bool TelegramInit(void) {
if (strlen(SettingsText(SET_TELEGRAM_TOKEN))) {
if (!telegramClient) {
telegramClient = new BearSSL::WiFiClientSecure_light(tls_rx_size, tls_tx_size);
#ifdef USE_MQTT_TLS_CA_CERT
telegramClient->setTrustAnchor(&GoDaddyCAG2_TA, 1);
#else
telegramClient->setPubKeyFingerprint(Telegram_Fingerprint, Telegram_Fingerprint, false); // check server fingerprint
#endif
if (Settings.flag5.tls_use_fingerprint) {
telegramClient->setPubKeyFingerprint(Telegram_Fingerprint, Telegram_Fingerprint, false); // check server fingerprint
} else {
telegramClient->setTrustAnchor(&GoDaddyCAG2_TA, 1);
}
Telegram.message_count = 0; // Number of received messages
Telegram.next_update_id = 0; // Code of last read Message
Telegram.message[0].text = "";