Support for AWS IoT via TLS 1.2

This commit is contained in:
Stephan Hadinger 2019-06-05 11:44:52 +02:00
parent 164b3aaf11
commit bc3d0add4c
11 changed files with 1559 additions and 13 deletions

BIN
lib_bearssl/libbearssl.a Normal file

Binary file not shown.

View File

@ -70,14 +70,16 @@ build_flags = ${esp82xx_defaults.build_flags}
platform = espressif8266@~2.2.1
build_flags = ${esp82xx_defaults.build_flags}
-Wl,-Teagle.flash.1m.ld
; Code optimization see https://github.com/esp8266/Arduino/issues/5790#issuecomment-475672473
; Code optimization see https://github.com/esp8266/Arduino/issues/5790#issuecomment-475672473
-O2
-DBEARSSL_SSL_BASIC
; link with an memory optimized version of lib_bearssl
-Llib_bearssl -lbearssl
; nonos-sdk 22x
-DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x
; nonos-sdk-pre-v3
; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3
; lwIP 1.4
; lwIP 1.4
; -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH
; lwIP 2 - Low Memory
; -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
@ -96,13 +98,15 @@ build_flags = ${esp82xx_defaults.build_flags}
platform = https://github.com/platformio/platform-espressif8266.git#feature/stage
build_flags = ${esp82xx_defaults.build_flags}
-Wl,-Teagle.flash.1m.ld
; Code optimization see https://github.com/esp8266/Arduino/issues/5790#issuecomment-475672473
; Code optimization see https://github.com/esp8266/Arduino/issues/5790#issuecomment-475672473
-O2
-DBEARSSL_SSL_BASIC
; nonos-sdk 22x
-DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x
; if you compile for AWS IoT, you should set MQTT keep alive to at least 30s instead of 10s, include is required here for PubSubClient lib
-DMQTT_KEEPALIVE=30
; nonos-sdk-pre-v3
; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3
; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3
; lwIP 1.4
; -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH
; lwIP 2 - Low Memory

138
sonoff/StackThunk_light.cpp Normal file
View File

@ -0,0 +1,138 @@
/*
StackThunk_light.c - Allow use second stack for BearSSL calls
Light version with reduced Stack size due to Tasmota optimizations.
BearSSL uses a significant amount of stack space, much larger than
the default Arduino core stack. These routines handle swapping
between a secondary, user-allocated stack on the heap and the real
stack.
Copyright (c) 2017 Earle F. Philhower, III. All rights reserved.
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
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#include <stdint.h>
#include <stdlib.h>
#include "StackThunk_light.h"
#include <ets_sys.h>
extern "C" {
uint32_t *stack_thunk_light_ptr = NULL;
uint32_t *stack_thunk_light_top = NULL;
uint32_t *stack_thunk_light_save = NULL; /* Saved A1 while in BearSSL */
uint32_t stack_thunk_light_refcnt = 0;
//#define _stackSize (5600/4)
#define _stackSize (5100/4) // using a light version of bearssl we can save 1KB
#define _stackPaint 0xdeadbeef
/* Add a reference, and allocate the stack if necessary */
void stack_thunk_light_add_ref()
{
stack_thunk_light_refcnt++;
if (stack_thunk_light_refcnt == 1) {
stack_thunk_light_ptr = (uint32_t *)malloc(_stackSize * sizeof(uint32_t));
stack_thunk_light_top = stack_thunk_light_ptr + _stackSize - 1;
stack_thunk_light_save = NULL;
stack_thunk_light_repaint();
}
}
/* Drop a reference, and free stack if no more in use */
void stack_thunk_light_del_ref()
{
if (stack_thunk_light_refcnt == 0) {
/* Error! */
return;
}
stack_thunk_light_refcnt--;
if (!stack_thunk_light_refcnt) {
free(stack_thunk_light_ptr);
stack_thunk_light_ptr = NULL;
stack_thunk_light_top = NULL;
stack_thunk_light_save = NULL;
}
}
void stack_thunk_light_repaint()
{
if (stack_thunk_light_ptr) {
for (int i=0; i < _stackSize; i++) {
stack_thunk_light_ptr[i] = _stackPaint;
}
}
}
/* Simple accessor functions used by postmortem */
uint32_t stack_thunk_light_get_refcnt() {
return stack_thunk_light_refcnt;
}
uint32_t stack_thunk_light_get_stack_top() {
return (uint32_t)stack_thunk_light_top;
}
uint32_t stack_thunk_light_get_stack_bot() {
return (uint32_t)stack_thunk_light_ptr;
}
uint32_t stack_thunk_light_get_cont_sp() {
return (uint32_t)stack_thunk_light_save;
}
/* Return the number of bytes ever used since the stack was created */
uint32_t stack_thunk_light_get_max_usage()
{
uint32_t cnt = 0;
/* No stack == no usage by definition! */
if (!stack_thunk_light_ptr) {
return 0;
}
for (cnt=0; (cnt < _stackSize) && (stack_thunk_light_ptr[cnt] == _stackPaint); cnt++) {
/* Noop, all work done in for() */
}
return 4 * (_stackSize - cnt);
}
/* Print the stack from the first used 16-byte chunk to the top, decodable by the exception decoder */
void stack_thunk_light_dump_stack()
{
uint32_t *pos = stack_thunk_light_top;
while (pos < stack_thunk_light_ptr) {
if ((pos[0] != _stackPaint) || (pos[1] != _stackPaint) || (pos[2] != _stackPaint) || (pos[3] != _stackPaint))
break;
pos += 4;
}
ets_printf(">>>stack>>>\n");
while (pos < stack_thunk_light_ptr) {
ets_printf("%08x: %08x %08x %08x %08x\n", (int32_t)pos, pos[0], pos[1], pos[2], pos[3]);
pos += 4;
}
ets_printf("<<<stack<<<\n");
}
/* Called when the stack overflow is detected by a thunk. Main memory is corrupted at this point. Do not return. */
void stack_thunk_light_fatal_overflow()
{
ets_printf("FATAL ERROR: BSSL stack overflow\n");
abort();
}
};

93
sonoff/StackThunk_light.h Normal file
View File

@ -0,0 +1,93 @@
/*
StackThunk_light.h - Allow use second stack for BearSSL calls
Light version with reduced Stack size due to Tasmota optimizations.
BearSSL uses a significant amount of stack space, much larger than
the default Arduino core stack. These routines handle swapping
between a secondary, user-allocated stack on the heap and the real
stack.
Copyright (c) 2017 Earle F. Philhower, III. All rights reserved.
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
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#ifndef _STACKTHUNK_LIGHT_H
#define _STACKTHUNK__LIGHTH
#ifdef __cplusplus
extern "C" {
#endif
extern void stack_thunk_light_add_ref();
extern void stack_thunk_light_del_ref();
extern void stack_thunk_light_repaint();
extern uint32_t stack_thunk_light_get_refcnt();
extern uint32_t stack_thunk_light_get_stack_top();
extern uint32_t stack_thunk_light_get_stack_bot();
extern uint32_t stack_thunk_light_get_cont_sp();
extern uint32_t stack_thunk_light_get_max_usage();
extern void stack_thunk_light_dump_stack();
extern void stack_thunk_light_fatal_overflow();
// Globals required for thunking operation
extern uint32_t *stack_thunk_light_ptr;
extern uint32_t *stack_thunk_light_top;
extern uint32_t *stack_thunk_light_save;
extern uint32_t stack_thunk_light_refcnt;
// Thunking macro
#define make_stack_thunk_light(fcnToThunk) \
__asm("\n\
.text\n\
.literal_position\n\
.literal .LC_STACK_VALUE"#fcnToThunk", 0xdeadbeef\n\
\n\
.text\n\
.global thunk_light_"#fcnToThunk"\n\
.type thunk_light_"#fcnToThunk", @function\n\
.align 4\n\
thunk_light_"#fcnToThunk":\n\
addi a1, a1, -16 /* Allocate space for saved registers on stack */\n\
s32i a0, a1, 12 /* Store A0, trounced by calls */\n\
s32i a15, a1, 8 /* Store A15 (our temporary one) */\n\
movi a15, stack_thunk_light_save /* Store A1(SP) in temp space */\n\
s32i a1, a15, 0\n\
movi a15, stack_thunk_light_top /* Load A1(SP) with thunk stack */\n\
l32i.n a1, a15, 0\n\
call0 "#fcnToThunk" /* Do the call */\n\
/* Check the stack canary wasn't overwritten */\n\
movi a15, stack_thunk_light_ptr\n\
l32i.n a15, a15, 0 /* A15 now has the pointer to stack end*/ \n\
l32i.n a15, a15, 0 /* A15 now has contents of last stack entry */\n\
l32r a0, .LC_STACK_VALUE"#fcnToThunk" /* A0 now has the check value */\n\
beq a0, a15, .L1"#fcnToThunk"\n\
call0 stack_thunk_light_fatal_overflow\n\
.L1"#fcnToThunk":\n\
movi a15, stack_thunk_light_save /* Restore A1(SP) */\n\
l32i.n a1, a15, 0\n\
l32i.n a15, a1, 8 /* Restore the saved registers */\n\
l32i.n a0, a1, 12\n\
addi a1, a1, 16 /* Free up stack and return to caller */\n\
ret\n\
.size thunk_light_"#fcnToThunk", . - thunk_light_"#fcnToThunk"\n");
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,882 @@
/*
WiFiClientBearSSL- SSL client/server for esp8266 using BearSSL libraries
- Mostly compatible with Arduino WiFi shield library and standard
WiFiClient/ServerSecure (except for certificate handling).
Copyright (c) 2018 Earle F. Philhower, III
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
*/
#define LWIP_INTERNAL
#include <list>
#include <errno.h>
#include <algorithm>
extern "C" {
#include "osapi.h"
#include "ets_sys.h"
}
#include "debug.h"
#include "ESP8266WiFi.h"
#include "WiFiClient.h"
#include "WiFiClientSecureLightBearSSL.h"
#include "StackThunk_light.h"
#include "lwip/opt.h"
#include "lwip/ip.h"
#include "lwip/tcp.h"
#include "lwip/inet.h"
#include "lwip/netif.h"
#include <include/ClientContext.h>
#include "c_types.h"
#include "coredecls.h"
#define SKEY_ON_STACK // copy private key+cert on stack rather than on heap, this works for now because it takes ~800 bytes
//#define DEBUG_TLS
#ifdef DEBUG_TLS
#define LOG_HEAP_SIZE(a) _Log_heap_size(a)
void _Log_heap_size(const char *msg) {
register uint32_t *sp asm("a1");
int freestack = 4 * (sp - g_pcont->stack);
Serial.printf("%s %d, Fragmentation=%d, Thunkstack=%d, Free stack=%d, FreeContStack=%d\n",
msg, ESP.getFreeHeap(), ESP.getHeapFragmentation(), stack_thunk_light_get_max_usage(),
freestack, ESP.getFreeContStack());
}
#else
#define LOG_HEAP_SIZE(a)
#endif
// Stack thunked versions of calls
// Initially in BearSSLHelpers.h
extern "C" {
extern unsigned char *thunk_light_br_ssl_engine_recvapp_buf( const br_ssl_engine_context *cc, size_t *len);
extern void thunk_light_br_ssl_engine_recvapp_ack(br_ssl_engine_context *cc, size_t len);
extern unsigned char *thunk_light_br_ssl_engine_recvrec_buf( const br_ssl_engine_context *cc, size_t *len);
extern void thunk_light_br_ssl_engine_recvrec_ack(br_ssl_engine_context *cc, size_t len);
extern unsigned char *thunk_light_br_ssl_engine_sendapp_buf( const br_ssl_engine_context *cc, size_t *len);
extern void thunk_light_br_ssl_engine_sendapp_ack(br_ssl_engine_context *cc, size_t len);
extern unsigned char *thunk_light_br_ssl_engine_sendrec_buf( const br_ssl_engine_context *cc, size_t *len);
extern void thunk_light_br_ssl_engine_sendrec_ack(br_ssl_engine_context *cc, size_t len);
};
// Second stack thunked helpers
make_stack_thunk_light(br_ssl_engine_recvapp_ack);
make_stack_thunk_light(br_ssl_engine_recvapp_buf);
make_stack_thunk_light(br_ssl_engine_recvrec_ack);
make_stack_thunk_light(br_ssl_engine_recvrec_buf);
make_stack_thunk_light(br_ssl_engine_sendapp_ack);
make_stack_thunk_light(br_ssl_engine_sendapp_buf);
make_stack_thunk_light(br_ssl_engine_sendrec_ack);
make_stack_thunk_light(br_ssl_engine_sendrec_buf);
// create new version of Thunk function to store on SYS stack
// unless the Thunk was initialized. Thanks to AES128 GCM, we can keep
// symetric processing on the stack
void min_br_ssl_engine_recvapp_ack(br_ssl_engine_context *cc, size_t len) {
if (stack_thunk_light_get_refcnt()) {
return thunk_light_br_ssl_engine_recvapp_ack(cc, len);
} else {
return br_ssl_engine_recvapp_ack(cc, len);
}
}
unsigned char *min_br_ssl_engine_recvapp_buf(const br_ssl_engine_context *cc, size_t *len) {
if (stack_thunk_light_get_refcnt()) {
return thunk_light_br_ssl_engine_recvapp_buf(cc, len);
} else {
return br_ssl_engine_recvapp_buf(cc, len);
}
}
void min_br_ssl_engine_recvrec_ack(br_ssl_engine_context *cc, size_t len) {
if (stack_thunk_light_get_refcnt()) {
return thunk_light_br_ssl_engine_recvrec_ack(cc, len);
} else {
return br_ssl_engine_recvrec_ack(cc, len);
}
}
unsigned char *min_br_ssl_engine_recvrec_buf(const br_ssl_engine_context *cc, size_t *len) {
if (stack_thunk_light_get_refcnt()) {
return thunk_light_br_ssl_engine_recvrec_buf(cc, len);
} else {
return br_ssl_engine_recvrec_buf(cc, len);
}
}
void min_br_ssl_engine_sendapp_ack(br_ssl_engine_context *cc, size_t len) {
if (stack_thunk_light_get_refcnt()) {
return thunk_light_br_ssl_engine_sendapp_ack(cc, len);
} else {
return br_ssl_engine_sendapp_ack(cc, len);
}
}
unsigned char *min_br_ssl_engine_sendapp_buf(const br_ssl_engine_context *cc, size_t *len) {
if (stack_thunk_light_get_refcnt()) {
return thunk_light_br_ssl_engine_sendapp_buf(cc, len);
} else {
return br_ssl_engine_sendapp_buf(cc, len);
}
}
void min_br_ssl_engine_sendrec_ack(br_ssl_engine_context *cc, size_t len) {
if (stack_thunk_light_get_refcnt()) {
return thunk_light_br_ssl_engine_sendrec_ack(cc, len);
} else {
return br_ssl_engine_sendrec_ack(cc, len);
}
}
unsigned char *min_br_ssl_engine_sendrec_buf(const br_ssl_engine_context *cc, size_t *len) {
if (stack_thunk_light_get_refcnt()) {
return thunk_light_br_ssl_engine_sendrec_buf(cc, len);
} else {
return br_ssl_engine_sendrec_buf(cc, len);
}
}
// Use min_ instead of original thunk_
#define br_ssl_engine_recvapp_ack min_br_ssl_engine_recvapp_ack
#define br_ssl_engine_recvapp_buf min_br_ssl_engine_recvapp_buf
#define br_ssl_engine_recvrec_ack min_br_ssl_engine_recvrec_ack
#define br_ssl_engine_recvrec_buf min_br_ssl_engine_recvrec_buf
#define br_ssl_engine_sendapp_ack min_br_ssl_engine_sendapp_ack
#define br_ssl_engine_sendapp_buf min_br_ssl_engine_sendapp_buf
#define br_ssl_engine_sendrec_ack min_br_ssl_engine_sendrec_ack
#define br_ssl_engine_sendrec_buf min_br_ssl_engine_sendrec_buf
//#define DEBUG_ESP_SSL
#ifdef DEBUG_ESP_SSL
#define DEBUG_BSSL(fmt, ...) DEBUG_ESP_PORT.printf_P((PGM_P)PSTR( "BSSL:" fmt), ## __VA_ARGS__)
//#define DEBUG_BSSL(fmt, ...) Serial.printf(fmt, ## __VA_ARGS__)
#else
#define DEBUG_BSSL(...)
#endif
namespace BearSSL {
void WiFiClientSecure_light::_clear() {
// TLS handshake may take more than the 5 second default timeout
_timeout = 10000; // 10 seconds max, it should never go over 6 seconds
_sc = nullptr;
_ctx_present = false;
_eng = nullptr;
_iobuf_in = nullptr;
_iobuf_out = nullptr;
_now = 0; // You can override or ensure time() is correct w/configTime
setBufferSizes(1024, 1024); // reasonable minimum
_handshake_done = false;
_last_error = 0;
_recvapp_buf = nullptr;
_recvapp_len = 0;
_fingerprint_any = true; // by default accept all fingerprints
_fingerprint1 = nullptr;
_fingerprint2 = nullptr;
}
// Constructor
WiFiClientSecure_light::WiFiClientSecure_light(int recv, int xmit) : WiFiClient() {
_clear();
LOG_HEAP_SIZE("StackThunk before");
//stack_thunk_light_add_ref();
LOG_HEAP_SIZE("StackThunk after");
// now finish the setup
setBufferSizes(recv, xmit); // reasonable minimum
allocateBuffers();
}
WiFiClientSecure_light::~WiFiClientSecure_light() {
if (_client) {
_client->unref();
_client = nullptr;
}
//_cipher_list = nullptr; // std::shared will free if last reference
_freeSSL();
}
void WiFiClientSecure_light::allocateBuffers(void) {
// We prefer to allocate all buffers at start, rather than lazy allocation and deallocation
// in the long run it avoids heap fragmentation and improves stability
LOG_HEAP_SIZE("allocateBuffers before");
_sc = std::make_shared<br_ssl_client_context>();
LOG_HEAP_SIZE("allocateBuffers ClientContext");
_iobuf_in = std::shared_ptr<unsigned char>(new unsigned char[_iobuf_in_size], std::default_delete<unsigned char[]>());
_iobuf_out = std::shared_ptr<unsigned char>(new unsigned char[_iobuf_out_size], std::default_delete<unsigned char[]>());
LOG_HEAP_SIZE("allocateBuffers after");
}
void WiFiClientSecure_light::setClientECCert(const br_x509_certificate *cert, const br_ec_private_key *sk,
unsigned allowed_usages, unsigned cert_issuer_key_type) {
_chain_P = cert;
_chain.data_len = _chain_P->data_len;
_chain.data = nullptr;
_sk_ec_P = sk;
_sk_ec.curve = _sk_ec_P->curve;
_sk_ec.xlen = _sk_ec_P->xlen;
_sk_ec.x = nullptr;
_allowed_usages = allowed_usages;
_cert_issuer_key_type = cert_issuer_key_type;
}
void WiFiClientSecure_light::setBufferSizes(int recv, int xmit) {
// Following constants taken from bearssl/src/ssl/ssl_engine.c (not exported unfortunately)
const int MAX_OUT_OVERHEAD = 85;
const int MAX_IN_OVERHEAD = 325;
// The data buffers must be between 512B and 16KB
recv = std::max(512, std::min(16384, recv));
xmit = std::max(512, std::min(16384, xmit));
// Add in overhead for SSL protocol
recv += MAX_IN_OVERHEAD;
xmit += MAX_OUT_OVERHEAD;
_iobuf_in_size = recv;
_iobuf_out_size = xmit;
}
bool WiFiClientSecure_light::stop(unsigned int maxWaitMs) {
bool ret = WiFiClient::stop(maxWaitMs); // calls our virtual flush()
_freeSSL();
return ret;
}
bool WiFiClientSecure_light::flush(unsigned int maxWaitMs) {
(void) _run_until(BR_SSL_SENDAPP);
return WiFiClient::flush(maxWaitMs);
}
int WiFiClientSecure_light::connect(IPAddress ip, uint16_t port) {
clearLastError();
if (!WiFiClient::connect(ip, port)) {
setLastError(ERR_TCP_CONNECT);
return 0;
}
return _connectSSL(nullptr);
}
int WiFiClientSecure_light::connect(const char* name, uint16_t port) {
IPAddress remote_addr;
clearLastError();
if (!WiFi.hostByName(name, remote_addr)) {
DEBUG_BSSL("connect: Name loopup failure\n");
setLastError(ERR_CANT_RESOLVE_IP);
return 0;
}
if (!WiFiClient::connect(remote_addr, port)) {
DEBUG_BSSL("connect: Unable to connect TCP socket\n");
_last_error = ERR_TCP_CONNECT;
return 0;
}
LOG_HEAP_SIZE("Before calling _connectSSL");
return _connectSSL(name);
}
int WiFiClientSecure_light::connect(const String& host, uint16_t port) {
return connect(host.c_str(), port);
}
void WiFiClientSecure_light::_freeSSL() {
_ctx_present = false;
_recvapp_buf = nullptr;
_recvapp_len = 0;
// This connection is toast
_handshake_done = false;
}
bool WiFiClientSecure_light::_clientConnected() {
return (_client && _client->state() == ESTABLISHED);
}
uint8_t WiFiClientSecure_light::connected() {
if (available() || (_clientConnected() && _handshake_done)) {
return true;
}
return false;
}
size_t WiFiClientSecure_light::_write(const uint8_t *buf, size_t size, bool pmem) {
size_t sent_bytes = 0;
if (!connected() || !size || !_handshake_done) {
return 0;
}
do {
// Ensure we yield if we need multiple fragments to avoid WDT
if (sent_bytes) {
optimistic_yield(1000);
}
// Get BearSSL to a state where we can send
if (_run_until(BR_SSL_SENDAPP) < 0) {
break;
}
if (br_ssl_engine_current_state(_eng) & BR_SSL_SENDAPP) {
size_t sendapp_len;
unsigned char *sendapp_buf = br_ssl_engine_sendapp_buf(_eng, &sendapp_len);
int to_send = size > sendapp_len ? sendapp_len : size;
if (pmem) {
memcpy_P(sendapp_buf, buf, to_send);
} else {
memcpy(sendapp_buf, buf, to_send);
}
br_ssl_engine_sendapp_ack(_eng, to_send);
br_ssl_engine_flush(_eng, 0);
flush();
buf += to_send;
sent_bytes += to_send;
size -= to_send;
} else {
break;
}
} while (size);
LOG_HEAP_SIZE("_write");
return sent_bytes;
}
size_t WiFiClientSecure_light::write(const uint8_t *buf, size_t size) {
return _write(buf, size, false);
}
size_t WiFiClientSecure_light::write_P(PGM_P buf, size_t size) {
return _write((const uint8_t *)buf, size, true);
}
// We have to manually read and send individual chunks.
size_t WiFiClientSecure_light::write(Stream& stream) {
size_t totalSent = 0;
size_t countRead;
size_t countSent;
if (!connected() || !_handshake_done) {
DEBUG_BSSL("write: Connect/handshake not completed yet\n");
return 0;
}
do {
uint8_t temp[256]; // Temporary chunk size same as ClientContext
countSent = 0;
countRead = stream.readBytes(temp, sizeof(temp));
if (countRead) {
countSent = _write((const uint8_t*)temp, countRead, true);
totalSent += countSent;
}
yield(); // Feed the WDT
} while ((countSent == countRead) && (countSent > 0));
return totalSent;
}
int WiFiClientSecure_light::read(uint8_t *buf, size_t size) {
if (!ctx_present() || !_handshake_done) {
return -1;
}
int avail = available();
bool conn = connected();
if (!avail && conn) {
return 0; // We're still connected, but nothing to read
}
if (!avail && !conn) {
DEBUG_BSSL("read: Not connected, none left available\n");
return -1;
}
if (avail) {
// Take data from the recvapp buffer
int to_copy = _recvapp_len < size ? _recvapp_len : size;
memcpy(buf, _recvapp_buf, to_copy);
br_ssl_engine_recvapp_ack(_eng, to_copy);
_recvapp_buf = nullptr;
_recvapp_len = 0;
return to_copy;
}
if (!conn) {
DEBUG_BSSL("read: Not connected\n");
return -1;
}
return 0; // If we're connected, no error but no read.
}
int WiFiClientSecure_light::read() {
uint8_t c;
if (1 == read(&c, 1)) {
return c;
}
DEBUG_BSSL("read: failed\n");
return -1;
}
int WiFiClientSecure_light::available() {
if (_recvapp_buf) {
return _recvapp_len; // Anything from last call?
}
_recvapp_buf = nullptr;
_recvapp_len = 0;
if (!ctx_present() || _run_until(BR_SSL_RECVAPP, false) < 0) {
return 0;
}
int st = br_ssl_engine_current_state(_eng);
if (st == BR_SSL_CLOSED) {
return 0; // Nothing leftover, SSL is closed
}
if (st & BR_SSL_RECVAPP) {
_recvapp_buf = br_ssl_engine_recvapp_buf(_eng, &_recvapp_len);
return _recvapp_len;
}
return 0;
}
int WiFiClientSecure_light::peek() {
if (!ctx_present() || !available()) {
DEBUG_BSSL("peek: Not connected, none left available\n");
return -1;
}
if (_recvapp_buf && _recvapp_len) {
return _recvapp_buf[0];
}
DEBUG_BSSL("peek: No data left\n");
return -1;
}
size_t WiFiClientSecure_light::peekBytes(uint8_t *buffer, size_t length) {
size_t to_copy = 0;
if (!ctx_present()) {
DEBUG_BSSL("peekBytes: Not connected\n");
return 0;
}
_startMillis = millis();
while ((available() < (int) length) && ((millis() - _startMillis) < 5000)) {
yield();
}
to_copy = _recvapp_len < length ? _recvapp_len : length;
memcpy(buffer, _recvapp_buf, to_copy);
return to_copy;
}
/* --- Copied almost verbatim from BEARSSL SSL_IO.C ---
Run the engine, until the specified target state is achieved, or
an error occurs. The target state is SENDAPP, RECVAPP, or the
combination of both (the combination matches either). When a match is
achieved, this function returns 0. On error, it returns -1.
*/
int WiFiClientSecure_light::_run_until(unsigned target, bool blocking) {
//LOG_HEAP_SIZE("_run_until 1");
if (!ctx_present()) {
DEBUG_BSSL("_run_until: Not connected\n");
return -1;
}
for (int no_work = 0; blocking || no_work < 2;) {
if (blocking) {
// Only for blocking operations can we afford to yield()
optimistic_yield(100);
}
int state;
state = br_ssl_engine_current_state(_eng);
if (state & BR_SSL_CLOSED) {
return -1;
}
if (!(_client->state() == ESTABLISHED) && !WiFiClient::available()) {
return (state & target) ? 0 : -1;
}
/*
If there is some record data to send, do it. This takes
precedence over everything else.
*/
if (state & BR_SSL_SENDREC) {
unsigned char *buf;
size_t len;
int wlen;
buf = br_ssl_engine_sendrec_buf(_eng, &len);
wlen = WiFiClient::write(buf, len);
if (wlen <= 0) {
/*
If we received a close_notify and we
still send something, then we have our
own response close_notify to send, and
the peer is allowed by RFC 5246 not to
wait for it.
*/
return -1;
}
if (wlen > 0) {
br_ssl_engine_sendrec_ack(_eng, wlen);
}
no_work = 0;
continue;
}
/*
If we reached our target, then we are finished.
*/
if (state & target) {
return 0;
}
/*
If some application data must be read, and we did not
exit, then this means that we are trying to write data,
and that's not possible until the application data is
read. This may happen if using a shared in/out buffer,
and the underlying protocol is not strictly half-duplex.
This is unrecoverable here, so we report an error.
*/
if (state & BR_SSL_RECVAPP) {
DEBUG_BSSL("_run_until: Fatal protocol state\n");
return -1;
}
/*
If we reached that point, then either we are trying
to read data and there is some, or the engine is stuck
until a new record is obtained.
*/
if (state & BR_SSL_RECVREC) {
if (WiFiClient::available()) {
unsigned char *buf;
size_t len;
int rlen;
buf = br_ssl_engine_recvrec_buf(_eng, &len);
rlen = WiFiClient::read(buf, len);
if (rlen < 0) {
return -1;
}
if (rlen > 0) {
br_ssl_engine_recvrec_ack(_eng, rlen);
}
no_work = 0;
continue;
}
}
/*
We can reach that point if the target RECVAPP, and
the state contains SENDAPP only. This may happen with
a shared in/out buffer. In that case, we must flush
the buffered data to "make room" for a new incoming
record.
*/
br_ssl_engine_flush(_eng, 0);
no_work++; // We didn't actually advance here
}
// We only get here if we ran through the loop without getting anything done
return -1;
}
bool WiFiClientSecure_light::_wait_for_handshake() {
_handshake_done = false;
while (!_handshake_done && _clientConnected()) {
int ret = _run_until(BR_SSL_SENDAPP);
if (ret < 0) {
DEBUG_BSSL("_wait_for_handshake: failed\n");
break;
}
if (br_ssl_engine_current_state(_eng) & BR_SSL_SENDAPP) {
_handshake_done = true;
}
optimistic_yield(1000);
}
return _handshake_done;
}
static uint8_t htoi (unsigned char c)
{
if (c>='0' && c <='9') return c - '0';
else if (c>='A' && c<='F') return 10 + c - 'A';
else if (c>='a' && c<='f') return 10 + c - 'a';
else return 255;
}
extern "C" {
// see https://stackoverflow.com/questions/6357031/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-in-c
void tohex(unsigned char * in, size_t insz, char * out, size_t outsz) {
unsigned char * pin = in;
static const char * hex = "0123456789ABCDEF";
char * pout = out;
for(; pin < in+insz; pout +=3, pin++){
pout[0] = hex[(*pin>>4) & 0xF];
pout[1] = hex[ *pin & 0xF];
pout[2] = ':';
if (pout + 3 - out > outsz){
/* Better to truncate output string than overflow buffer */
/* it would be still better to either return a status */
/* or ensure the target buffer is large enough and it never happen */
break;
}
}
pout[-1] = 0;
}
// BearSSL doesn't define a true insecure decoder, so we make one ourselves
// from the simple parser. It generates the issuer and subject hashes and
// the SHA1 fingerprint, only one (or none!) of which will be used to
// "verify" the certificate.
// Private x509 decoder state
struct br_x509_pubkeyfingerprint_context {
const br_x509_class *vtable;
bool done_cert; // did we parse the first cert already?
bool fingerprint_all;
uint8_t *pubkey_recv_fingerprint;
const uint8_t *fingerprint1;
const uint8_t *fingerprint2;
unsigned usages; // pubkey usage
br_x509_decoder_context ctx; // defined in BearSSL
};
// Callback on the first byte of any certificate
static void pubkeyfingerprint_start_chain(const br_x509_class **ctx, const char *server_name) {
br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx;
// Don't process anything but the first certificate in the chain
if (!xc->done_cert) {
br_x509_decoder_init(&xc->ctx, nullptr, nullptr, nullptr, nullptr);
}
(void)server_name; // ignore server name
}
// Callback for each certificate present in the chain (but only operates
// on the first one by design).
static void pubkeyfingerprint_start_cert(const br_x509_class **ctx, uint32_t length) {
(void) ctx; // do nothing
(void) length;
}
// Callback for each byte stream in the chain. Only process first cert.
static void pubkeyfingerprint_append(const br_x509_class **ctx, const unsigned char *buf, size_t len) {
br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx;
// Don't process anything but the first certificate in the chain
if (!xc->done_cert) {
br_x509_decoder_push(&xc->ctx, (const void*)buf, len);
}
}
// Callback on individual cert end.
static void pubkeyfingerprint_end_cert(const br_x509_class **ctx) {
br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx;
xc->done_cert = true; // first cert already processed
}
static void pubkeyfingerprint_pubkey_fingerprint(br_sha1_context *shactx, br_rsa_public_key rsakey) {
br_sha1_init(shactx);
br_sha1_update(shactx, "ssh-rsa", 7); // tag
br_sha1_update(shactx, rsakey.e, rsakey.elen); // exponent
br_sha1_update(shactx, rsakey.n, rsakey.nlen); // modulus
}
// Callback when complete chain has been parsed.
// Return 0 on validation success, !0 on validation error
static unsigned pubkeyfingerprint_end_chain(const br_x509_class **ctx) {
br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx;
br_sha1_context sha1_context;
pubkeyfingerprint_pubkey_fingerprint(&sha1_context, xc->ctx.pkey.key.rsa);
br_sha1_out(&sha1_context, xc->pubkey_recv_fingerprint); // copy to fingerprint
if (!xc->fingerprint_all) {
if (0 == memcmp(xc->fingerprint1, xc->pubkey_recv_fingerprint, 20)) {
return 0;
}
if (0 == memcmp(xc->fingerprint2, xc->pubkey_recv_fingerprint, 20)) {
return 0;
}
return 1; // no match, error
} else {
// Default (no validation at all) or no errors in prior checks = success.
return 0;
}
}
// Return the public key from the validator (set by x509_minimal)
static const br_x509_pkey *pubkeyfingerprint_get_pkey(const br_x509_class *const *ctx, unsigned *usages) {
const br_x509_pubkeyfingerprint_context *xc = (const br_x509_pubkeyfingerprint_context *)ctx;
if (usages != NULL) {
*usages = BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN; // I said we were insecure!
}
return &xc->ctx.pkey;
}
// Set up the x509 insecure data structures for BearSSL core to use.
void br_x509_pubkeyfingerprint_init(br_x509_pubkeyfingerprint_context *ctx,
const uint8_t *fingerprint1, const uint8_t *fingerprint2,
uint8_t *recv_fingerprint,
bool fingerprint_all) {
static const br_x509_class br_x509_pubkeyfingerprint_vtable PROGMEM = {
sizeof(br_x509_pubkeyfingerprint_context),
pubkeyfingerprint_start_chain,
pubkeyfingerprint_start_cert,
pubkeyfingerprint_append,
pubkeyfingerprint_end_cert,
pubkeyfingerprint_end_chain,
pubkeyfingerprint_get_pkey
};
memset(ctx, 0, sizeof * ctx);
ctx->vtable = &br_x509_pubkeyfingerprint_vtable;
ctx->done_cert = false;
ctx->fingerprint1 = fingerprint1;
ctx->fingerprint2 = fingerprint2;
ctx->pubkey_recv_fingerprint = recv_fingerprint;
ctx->fingerprint_all = fingerprint_all;
}
// We limit to a single cipher to reduce footprint
// we reference it, don't put in PROGMEM
static const uint16_t suites[] = {
BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
};
// Default initializion for our SSL clients
static void br_ssl_client_base_init(br_ssl_client_context *cc) {
br_ssl_client_zero(cc);
// forbid SSL renegociation, as we free the Private Key after handshake
br_ssl_engine_add_flags(&cc->eng, BR_OPT_NO_RENEGOTIATION);
br_ssl_engine_set_versions(&cc->eng, BR_TLS12, BR_TLS12);
br_ssl_engine_set_suites(&cc->eng, suites, (sizeof suites) / (sizeof suites[0]));
br_ssl_client_set_default_rsapub(cc);
br_ssl_engine_set_default_rsavrfy(&cc->eng);
// install hashes
br_ssl_engine_set_hash(&cc->eng, br_sha256_ID, &br_sha256_vtable);
br_ssl_engine_set_prf_sha256(&cc->eng, &br_tls12_sha256_prf);
// AES CTR/GCM small version, not contstant time (we don't really care here as there is no TPM anyways)
br_ssl_engine_set_gcm(&cc->eng, &br_sslrec_in_gcm_vtable, &br_sslrec_out_gcm_vtable);
br_ssl_engine_set_aes_ctr(&cc->eng, &br_aes_small_ctr_vtable);
br_ssl_engine_set_ghash(&cc->eng, &br_ghash_ctmul32);
// we support only P256 EC curve
br_ssl_engine_set_ec(&cc->eng, &br_ec_p256_m15);
}
}
// Called by connect() to do the actual SSL setup and handshake.
// Returns if the SSL handshake succeeded.
bool WiFiClientSecure_light::_connectSSL(const char* hostName) {
br_ec_private_key sk_ec;
br_x509_certificate chain;
#ifdef SKEY_ON_STACK
unsigned char chain_data[_chain_P->data_len];
unsigned char sk_data[_sk_ec_P->xlen];
#endif
br_x509_pubkeyfingerprint_context *x509_insecure;
LOG_HEAP_SIZE("_connectSSL.start");
stack_thunk_light_add_ref();
LOG_HEAP_SIZE("Thunk allocated");
DEBUG_BSSL("_connectSSL: start connection\n");
_freeSSL();
clearLastError();
_ctx_present = true;
_eng = &_sc->eng; // Allocation/deallocation taken care of by the _sc shared_ptr
br_ssl_client_base_init(_sc.get());
LOG_HEAP_SIZE("_connectSSL before DecoderContext allocation");
// Only failure possible in the installation is OOM
x509_insecure = (br_x509_pubkeyfingerprint_context*) malloc(sizeof(br_x509_pubkeyfingerprint_context));
br_x509_pubkeyfingerprint_init(x509_insecure, _fingerprint1, _fingerprint2,
_recv_fingerprint, _fingerprint_any);
br_ssl_engine_set_x509(_eng, &x509_insecure->vtable);
br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size);
LOG_HEAP_SIZE("_connectSSL after PrivKey allocation");
// allocate Private key and client certificate
//chain = new X509List(_chain_PEM);
//sk = new PrivateKey(_sk_PEM);
chain.data_len = _chain_P->data_len;
#ifdef SKEY_ON_STACK // allocate on stack
chain.data = &chain_data[0];
#else // allocate with malloc
chain.data = (unsigned char *) malloc(chain.data_len);
#endif
if (chain.data) memcpy_P(chain.data, _chain_P->data, chain.data_len);
sk_ec.curve = _sk_ec_P->curve;
sk_ec.xlen = _sk_ec_P->xlen;
#ifdef SKEY_ON_STACK
sk_ec.x = &sk_data[0];
#else
sk_ec.x = (unsigned char *) malloc(sk_ec.xlen);
#endif
if (sk_ec.x) memcpy_P(sk_ec.x, _sk_ec_P->x, sk_ec.xlen);
LOG_HEAP_SIZE("_connectSSL after PrivKey allocation");
// check if memory was correctly allocated
if ((!stack_thunk_light_get_stack_bot()) || (!x509_insecure) ||
(!chain.data) || (!sk_ec.x)) {
// memory allocation problem
setLastError(ERR_OOM);
#ifndef SKEY_ON_STACK
free(chain.data);
free(sk_ec.x);
#endif
free(x509_insecure);
stack_thunk_light_del_ref();
DEBUG_BSSL("_connectSSL: Out of memory\n");
return false;
}
// limited to P256 curve
br_ssl_client_set_single_ec(_sc.get(), &chain, 1,
&sk_ec, _allowed_usages,
_cert_issuer_key_type, &br_ec_p256_m15, br_ecdsa_sign_asn1_get_default());
if (!br_ssl_client_reset(_sc.get(), hostName, 0)) {
#ifndef SKEY_ON_STACK
free(chain.data);
free(sk_ec.x);
#endif
free(x509_insecure);
stack_thunk_light_del_ref();
_freeSSL();
DEBUG_BSSL("_connectSSL: Can't reset client\n");
return false;
}
auto ret = _wait_for_handshake();
#ifdef DEBUG_ESP_SSL
if (!ret) {
DEBUG_BSSL("Couldn't connect. Error = %d\n", getLastError());
} else {
DEBUG_BSSL("Connected! MFLNStatus = %d\n", getMFLNStatus());
}
#endif
LOG_HEAP_SIZE("_connectSSL.end");
stack_thunk_light_del_ref();
//stack_thunk_light_repaint();
LOG_HEAP_SIZE("_connectSSL.end, freeing StackThunk");
#ifndef SKEY_ON_STACK
free(chain.data);
free(sk_ec.x);
#endif
free(x509_insecure);
LOG_HEAP_SIZE("_connectSSL after release of Priv Key");
return ret;
}
};

View File

@ -0,0 +1,148 @@
/*
WiFiClientBearSSL- SSL client/server for esp8266 using BearSSL libraries
- Mostly compatible with Arduino WiFi shield library and standard
WiFiClient/ServerSecure (except for certificate handling).
Copyright (c) 2018 Earle F. Philhower, III
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
*/
#ifndef wificlientlightbearssl_h
#define wificlientlightbearssl_h
#include <vector>
#include "WiFiClient.h"
#include <bearssl/bearssl.h>
#include "BearSSLHelpers.h"
#include "CertStoreBearSSL.h"
namespace BearSSL {
class WiFiClientSecure_light : public WiFiClient {
public:
WiFiClientSecure_light(int recv, int xmit);
~WiFiClientSecure_light() override;
void allocateBuffers(void);
int connect(IPAddress ip, uint16_t port) override;
int connect(const String& host, uint16_t port) override;
int connect(const char* name, uint16_t port) override;
uint8_t connected() override;
size_t write(const uint8_t *buf, size_t size) override;
size_t write_P(PGM_P buf, size_t size) override;
size_t write(const char *buf) {
return write((const uint8_t*)buf, strlen(buf));
}
size_t write_P(const char *buf) {
return write_P((PGM_P)buf, strlen_P(buf));
}
size_t write(Stream& stream); // Note this is not virtual
int read(uint8_t *buf, size_t size) override;
int available() override;
int read() override;
int peek() override;
size_t peekBytes(uint8_t *buffer, size_t length) override;
bool flush(unsigned int maxWaitMs);
bool stop(unsigned int maxWaitMs);
void flush() override { (void)flush(0); }
void stop() override { (void)stop(0); }
// Only check SHA1 fingerprint of public key
void setPubKeyFingerprint(const uint8_t *f1, const uint8_t *f2,
bool f_any = false) {
_fingerprint1 = f1;
_fingerprint2 = f2;
_fingerprint_any = f_any;
}
const uint8_t * getRecvPubKeyFingerprint(void) {
return _recv_fingerprint;
}
void setClientECCert(const br_x509_certificate *cert, const br_ec_private_key *sk,
unsigned allowed_usages, unsigned cert_issuer_key_type);
// Sets the requested buffer size for transmit and receive
void setBufferSizes(int recv, int xmit);
// Returns whether MFLN negotiation for the above buffer sizes succeeded (after connection)
int getMFLNStatus() {
return connected() && br_ssl_engine_get_mfln_negotiated(_eng);
}
int32_t getLastError(void) {
if (_last_error) {
return _last_error;
} else {
return br_ssl_engine_last_error(_eng);
}
}
inline void setLastError(int32_t err) {
_last_error = err;
}
inline void clearLastError(void) {
_last_error = 0;
}
private:
void _clear();
bool _ctx_present;
std::shared_ptr<br_ssl_client_context> _sc;
inline bool ctx_present() {
return _ctx_present;
}
br_ssl_engine_context *_eng; // &_sc->eng, to allow for client or server contexts
std::shared_ptr<unsigned char> _iobuf_in;
std::shared_ptr<unsigned char> _iobuf_out;
time_t _now;
int _iobuf_in_size;
int _iobuf_out_size;
bool _handshake_done;
uint64_t _last_error;
bool _fingerprint_any; // accept all fingerprints
const uint8_t *_fingerprint1; // fingerprint1 to be checked against
const uint8_t *_fingerprint2; // fingerprint2 to be checked against
uint8_t _recv_fingerprint[20]; // fingerprint received
unsigned char *_recvapp_buf;
size_t _recvapp_len;
bool _clientConnected(); // Is the underlying socket alive?
bool _connectSSL(const char *hostName); // Do initial SSL handshake
void _freeSSL();
int _run_until(unsigned target, bool blocking = true);
size_t _write(const uint8_t *buf, size_t size, bool pmem);
bool _wait_for_handshake(); // Sets and return the _handshake_done after connecting
// Optional client certificate
br_x509_certificate _chain; // local RAM copy
const br_x509_certificate *_chain_P; // PROGMEM certificate
br_ec_private_key _sk_ec;
const br_ec_private_key *_sk_ec_P; // PROGMEM private key
unsigned _allowed_usages;
unsigned _cert_issuer_key_type;
};
#define ERR_OOM -1000
#define ERR_CANT_RESOLVE_IP -1001
#define ERR_TCP_CONNECT -1002
};
#endif

View File

@ -88,7 +88,7 @@
#define MQTT_USE 1 // [SetOption3] Select default MQTT use (0 = Off, 1 = On)
#define MQTT_HOST "" // [MqttHost]
#define MQTT_FINGERPRINT1 "A5 02 FF 13 99 9F 8B 39 8E F1 83 4F 11 23 65 0B 32 36 FC 07" // [MqttFingerprint1]
#define MQTT_FINGERPRINT1 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" // [MqttFingerprint1]
#define MQTT_FINGERPRINT2 "A5 02 FF 13 99 9F 8B 39 8E F1 83 4F 11 23 65 0B 32 36 FC 07" // [MqttFingerprint2]
#define MQTT_PORT 1883 // [MqttPort] MQTT port (10123 on CloudMQTT)
#define MQTT_USER "DVES_USER" // [MqttUser] MQTT user
@ -267,6 +267,11 @@
//#define USE_MQTT_TLS // Use TLS for MQTT connection (+53k code, +15k mem)
// #define USE_MQTT_TLS_CA_CERT // Use LetsEncrypt Certificate from sonoff_letsencrypt.h - Not supported with core 2.3.0
// -- MQTT - Special version for AWS IoT
//#define USE_MQTT_AWS_IOT // Enable MQTT for AWS IoT - requires a private key (+56.7k code, +6.0k mem and +6.6k additional during connection handshake)
// you need to generate a private key + certificate per device
// and update 'sonoff/sonoff_aws_iot.cpp'
// -- KNX IP Protocol -----------------------------
//#define USE_KNX // Enable KNX IP Protocol Support (+9.4k code, +3k7 mem)
#define USE_KNX_WEB_MENU // Enable KNX WEB MENU (+8.3k code, +144 mem)
@ -473,4 +478,18 @@
#error "Select either USE_MQTT_TLS or USE_WEBSERVER as there is just not enough memory to play with"
#endif
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
#error "Select either USE_MQTT_TLS or USE_MQTT_AWS_IOT, they are not compatible"
#endif
#if defined(USE_DISCOVERY) && defined(USE_MQTT_AWS_IOT)
#error "Select either USE_DISCOVERY or USE_MQTT_AWS_IOT, mDNS takes too much code space and is not needed for AWS IoT"
#endif
#if defined(USE_MQTT_TLS) || defined(USE_MQTT_AWS_IOT)
#undef WEB_LOG_SIZE
#define WEB_LOG_SIZE (2000) // reduce log buffer size when using TLS
#endif
#endif // _MY_USER_CONFIG_H_

View File

@ -120,11 +120,7 @@ const uint16_t MIN_MESSZ = 893; // Min number of characters in MQTT
const uint8_t SENSOR_MAX_MISS = 5; // Max number of missed sensor reads before deciding it's offline
#ifdef USE_MQTT_TLS
const uint16_t WEB_LOG_SIZE = 2000; // Max number of characters in weblog
#else
const uint16_t WEB_LOG_SIZE = 4000; // Max number of characters in weblog
#endif
#define WEB_LOG_SIZE (4000) // Max number of characters in weblog
const uint8_t MAX_BACKLOG = 30; // Max number of commands in backlog
const uint32_t MIN_BACKLOG_DELAY = 2; // Minimal backlog delay in 0.1 seconds

147
sonoff/sonoff_aws_iot.cpp Normal file
View File

@ -0,0 +1,147 @@
/*
sonoff_aws_iot.cpp - TLS AWS IoT for Sonoff-Tasmota - Private key
Copyright (C) 2019 Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <bearssl/bearssl.h>
#include <pgmspace.h>
// nasty hack to force PROGMEM
#define static static PROGMEM
namespace aws_iot_privkey {
/*********************************************************************************************\
* Private key for AWS IoT
*
* Create the private key, generate the CSR and sign it with AWS IoT Console.
*
* Then generate C version of Private Key and Certificate, cut and paste below
*
* Downloaded from https://www.identrust.com/support/downloads
\*********************************************************************************************/
/*********************************************************************************************\
* Export Private Key as a C structure
*
* $ bearssl skey -C <private_key.PEM>
\*********************************************************************************************/
/* --------------- CUT AND PASTE PRIVATE KEY BELOW --------------- */
/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
static const unsigned char EC_X[] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77
};
static const br_ec_private_key EC = {
23,
(unsigned char *)EC_X, sizeof EC_X
};
/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
/* --------------- CUT AND PASTE PRIVATE KEY ABOVE --------------- */
/*********************************************************************************************\
* Export Private Key as a C structure
*
* $ bearssl chain <certificate.PEM>
\*********************************************************************************************/
/* --------------- CUT AND PASTE PRIVATE KEY BELOW --------------- */
/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
static const unsigned char CERT0[] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77
};
static const br_x509_certificate CHAIN[] = {
{ (unsigned char *)CERT0, sizeof CERT0 }
};
#define CHAIN_LEN 1
/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
/* --------------- CUT AND PASTE PRIVATE KEY ABOVE --------------- */
const br_ec_private_key *AWS_IoT_Private_Key = &EC;
const br_x509_certificate *AWS_IoT_Client_Certificate = &CHAIN[0];
}

View File

@ -419,7 +419,8 @@ void KNX_CB_Action(message_t const &msg, void *arg);
#endif
#ifndef MQTT_FINGERPRINT1
#define MQTT_FINGERPRINT1 "A5 02 FF 13 99 9F 8B 39 8E F1 83 4F 11 23 65 0B 32 36 FC 07"
// Set an all-zeros default fingerprint to activate auto-learning on first connection (AWS IoT)
#define MQTT_FINGERPRINT1 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
#endif
#ifndef MQTT_FINGERPRINT2
@ -434,7 +435,8 @@ void KNX_CB_Action(message_t const &msg, void *arg);
#define MQTT_MAX_PACKET_SIZE 1000 // Bytes
#endif
#ifndef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 15 // Seconds
//#define MQTT_KEEPALIVE 15 // Seconds
#define MQTT_KEEPALIVE 30 // Changed to 30s which is min for AWS IoT, hoping it does not break anthing
#endif
#ifndef MQTT_TIMEOUT
#define MQTT_TIMEOUT 10000 // milli seconds

View File

@ -24,6 +24,9 @@
#include "sonoff_letsencrypt.h" // LetsEncrypt certificate
#endif
WiFiClientSecure EspClient; // Wifi Secure Client
#elif defined(USE_MQTT_AWS_IOT)
#include "WiFiClientSecureLightBearSSL.h"
BearSSL::WiFiClientSecure_light *awsClient;
#else
WiFiClient EspClient; // Wifi Client
#endif
@ -46,6 +49,32 @@ uint8_t mqtt_initial_connection_state = 2; // MQTT connection messages state
bool mqtt_connected = false; // MQTT virtual connection status
bool mqtt_allowed = false; // MQTT enabled and parameters valid
#ifdef USE_MQTT_AWS_IOT
namespace aws_iot_privkey {
// this is where the Private Key and Certificate are stored
extern const br_ec_private_key *AWS_IoT_Private_Key;
extern const br_x509_certificate *AWS_IoT_Client_Certificate;
}
// A typical AWS IoT endpoint is 50 characters long, it does not fit
// in MqttHost field (32 chars). We need to concatenate both MqttUser and MqttHost
char AWS_endpoint[65]; // aWS IOT endpoint, concatenation of user and host
// check whether the fingerprint is filled with a single value
// Filled with 0x00 = accept any fingerprint and learn it for next time
// Filled with 0xFF = accept any fingerpring forever
bool is_fingerprint_mono_value(uint8_t finger[20], uint8_t value) {
for (uint32_t i = 0; i<20; i++) {
if (finger[i] != value) {
return false;
}
}
return true;
}
#endif // USE_MQTT_AWS_IOT
/*********************************************************************************************\
* MQTT driver specific code need to provide the following functions:
*
@ -62,7 +91,35 @@ bool mqtt_allowed = false; // MQTT enabled and parameters valid
#error "MQTT_MAX_PACKET_SIZE is too small in libraries/PubSubClient/src/PubSubClient.h, increase it to at least 1000"
#endif
#ifdef USE_MQTT_AWS_IOT
PubSubClient MqttClient;
#else
PubSubClient MqttClient(EspClient);
#endif
void MqttInit(void) {
#ifdef USE_MQTT_AWS_IOT
AWS_endpoint[0] = 0;
uint8_t len_user = strlen(Settings.mqtt_user);
uint8_t len_host = strlen(Settings.mqtt_host);
if (len_user > 0) {
strcpy(AWS_endpoint, Settings.mqtt_user);
if (('.' != AWS_endpoint[len_user-1]) && ('.' != Settings.mqtt_host[0])) {
AWS_endpoint[len_user++] = '.';
}
strcpy(&AWS_endpoint[len_user], Settings.mqtt_host);
}
awsClient = new BearSSL::WiFiClientSecure_light(1024,1024);
awsClient->setClientECCert(aws_iot_privkey::AWS_IoT_Client_Certificate,
aws_iot_privkey::AWS_IoT_Private_Key,
0xFFFF /* all usages, don't care */, 0);
MqttClient.setClient(*awsClient);
#endif
}
bool MqttIsConnected(void)
{
@ -179,6 +236,10 @@ void MqttPublishDirect(const char* topic, bool retained)
void MqttPublish(const char* topic, bool retained)
{
char *me;
#ifdef USE_MQTT_AWS_IOT
AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR("Retained is not supported by AWS IoT, using retained = false."));
retained = false; // AWS IoT does not support retained, it will disconnect if received
#endif
if (!strcmp(Settings.mqtt_prefix[0],Settings.mqtt_prefix[1])) {
me = strstr(topic,Settings.mqtt_prefix[0]);
@ -282,7 +343,11 @@ void MqttDisconnected(int state)
mqtt_connected = false;
mqtt_retry_counter = Settings.mqtt_retry;
#ifdef USE_MQTT_AWS_IOT
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND), AWS_endpoint, Settings.mqtt_port, state, mqtt_retry_counter);
#else
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND), Settings.mqtt_host, Settings.mqtt_port, state, mqtt_retry_counter);
#endif
rules_flag.mqtt_disconnected = 1;
}
@ -443,6 +508,8 @@ void MqttReconnect(void)
#ifdef USE_MQTT_TLS
EspClient = WiFiClientSecure(); // Wifi Secure Client reconnect issue 4497 (https://github.com/esp8266/Arduino/issues/4497)
#elif defined(USE_MQTT_AWS_IOT)
awsClient->stop();
#else
EspClient = WiFiClient(); // Wifi Client reconnect issue 4497 (https://github.com/esp8266/Arduino/issues/4497)
#endif
@ -456,7 +523,11 @@ void MqttReconnect(void)
}
MqttClient.setCallback(MqttDataHandler);
#ifdef USE_MQTT_AWS_IOT
MqttClient.setServer(AWS_endpoint, Settings.mqtt_port);
#else
MqttClient.setServer(Settings.mqtt_host, Settings.mqtt_port);
#endif
/*
// Skip MQTT host DNS lookup if not needed
uint32_t current_hash = GetHash(Settings.mqtt_host, strlen(Settings.mqtt_host));
@ -466,9 +537,50 @@ void MqttReconnect(void)
}
MqttClient.setServer(mqtt_host_addr, Settings.mqtt_port);
*/
uint32_t time = millis();
#ifdef USE_MQTT_AWS_IOT
bool allow_all_fingerprints = false;
bool learn_fingerprint1 = is_fingerprint_mono_value(Settings.mqtt_fingerprint[0], 0x00);
bool learn_fingerprint2 = is_fingerprint_mono_value(Settings.mqtt_fingerprint[1], 0x00);
allow_all_fingerprints |= is_fingerprint_mono_value(Settings.mqtt_fingerprint[0], 0xff);
allow_all_fingerprints |= is_fingerprint_mono_value(Settings.mqtt_fingerprint[1], 0xff);
allow_all_fingerprints |= learn_fingerprint1;
allow_all_fingerprints |= learn_fingerprint2;
awsClient->setPubKeyFingerprint(Settings.mqtt_fingerprint[0], Settings.mqtt_fingerprint[1], allow_all_fingerprints);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "AWS IoT endpoint: %s"), AWS_endpoint);
if (MqttClient.connect(mqtt_client, mqtt_user, mqtt_pwd, nullptr, 0, false, nullptr)) {
#else
if (MqttClient.connect(mqtt_client, mqtt_user, mqtt_pwd, stopic, 1, true, mqtt_data)) {
#endif
#ifdef USE_MQTT_AWS_IOT
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "AWS IoT connected in %d ms"), millis() - time);
if (learn_fingerprint1 || learn_fingerprint2) {
// we potentially need to learn the fingerprint just seen
bool fingerprint_matched = false;
const uint8_t *recv_fingerprint = awsClient->getRecvPubKeyFingerprint();
if (0 == memcmp(recv_fingerprint, Settings.mqtt_fingerprint[0], 20)) {
fingerprint_matched = true;
}
if (0 == memcmp(recv_fingerprint, Settings.mqtt_fingerprint[1], 20)) {
fingerprint_matched = true;
}
if (!fingerprint_matched) {
// we had no match, so we need to change all fingerprints ready to learn
if (learn_fingerprint1) {
memcpy(Settings.mqtt_fingerprint[0], recv_fingerprint, 20);
}
if (learn_fingerprint2) {
memcpy(Settings.mqtt_fingerprint[1], recv_fingerprint, 20);
}
restart_flag = 2; // save and restart
}
}
#endif
MqttConnected();
} else {
#ifdef USE_MQTT_AWS_IOT
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "AWS IoT connection error: %d"), awsClient->getLastError());
#endif
MqttDisconnected(MqttClient.state()); // status codes are documented here http://pubsubclient.knolleary.net/api.html#state
}
}
@ -549,7 +661,7 @@ bool MqttCommand(void)
}
Response_P(S_JSON_COMMAND_INDEX_SVALUE, command, index, GetStateText(index -1));
}
#ifdef USE_MQTT_TLS
#if defined(USE_MQTT_TLS) || defined(USE_MQTT_AWS_IOT)
else if ((CMND_MQTTFINGERPRINT == command_code) && (index > 0) && (index <= 2)) {
char fingerprint[60];
if ((data_len > 0) && (data_len < sizeof(fingerprint))) {
@ -827,6 +939,11 @@ bool Xdrv02(uint8_t function)
if (Settings.flag.mqtt_enabled) {
switch (function) {
#ifdef USE_MQTT_AWS_IOT
case FUNC_PRE_INIT:
MqttInit();
break;
#endif
case FUNC_EVERY_50_MSECOND: // https://github.com/knolleary/pubsubclient/issues/556
MqttClient.loop();
break;