From 97a70a140791953ee29a2ae8761c3f6f382b75d6 Mon Sep 17 00:00:00 2001 From: Tico06 Date: Fri, 8 Jul 2022 10:16:03 +0200 Subject: [PATCH] New TLS implementation (#1520) * New TLS implementation Implement TLS to mqtt server thanks to WiFiClientSecure class * New TLS implementation Implement TLS to mqtt server thanks to WiFiClientSecure class * New TLS implementation Implement TLS to mqtt server thanks to WiFiClientSecure class * New TLS implementation Implement TLS to mqtt server thanks to WiFiClientSecure class * Update MyConfig.h Typo * Update GatewayESP8266SecureMQTTClient.ino Typo * MyGatewayTransportMQTTClient.cpp updated Move tls settings to bool gatewayTransportInit(void) * MySensors code styling applied by GIT * Try to fix Doxygen warnings * Doxygen warnings fixed hopefuly * MY_GATEWAY_ESP8266_SECURE doc added * MY_GATEWAY_ESP8266_SECURE doc completed * Avoid platform cross compiling * Replaced spaces indent by tabs * Multilines comments to /* --- .ci/arduino.groovy | 9 +- MyConfig.h | 76 ++++++- core/MyGatewayTransportMQTTClient.cpp | 124 ++++++++++-- .../GatewayESP8266SecureMQTTClient.ino | 190 ++++++++++++++++++ .../GatewayESP8266SecureMQTTClient/certs.h | 148 ++++++++++++++ keywords.txt | 7 +- 6 files changed, 524 insertions(+), 30 deletions(-) mode change 100755 => 100644 core/MyGatewayTransportMQTTClient.cpp create mode 100644 examples/GatewayESP8266SecureMQTTClient/GatewayESP8266SecureMQTTClient.ino create mode 100644 examples/GatewayESP8266SecureMQTTClient/certs.h diff --git a/.ci/arduino.groovy b/.ci/arduino.groovy index 051271c03..05ff16797 100644 --- a/.ci/arduino.groovy +++ b/.ci/arduino.groovy @@ -51,6 +51,7 @@ def buildMySensorsMicro(config, sketches, String key) { for (sketch = 0; sketch < sketches.size(); sketch++) { if (sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266SecureMQTTClient/GatewayESP8266SecureMQTTClient.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && sketches[sketch].path != config.library_root+'examples/GatewayGSMMQTTClient/GatewayGSMMQTTClient.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP32/GatewayESP32.ino' && @@ -87,6 +88,7 @@ def buildMySensorsGw(config, sketches, String key) { if (sketches[sketch].path != config.library_root+'examples/BatteryPoweredSensor/BatteryPoweredSensor.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266SecureMQTTClient/GatewayESP8266SecureMQTTClient.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && sketches[sketch].path != config.library_root+'examples/GatewayGSMMQTTClient/GatewayGSMMQTTClient.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP32/GatewayESP32.ino' && @@ -123,6 +125,7 @@ def buildArduinoUno(config, sketches, String key) { for (sketch = 0; sketch < sketches.size(); sketch++) { if (sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266SecureMQTTClient/GatewayESP8266SecureMQTTClient.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP32/GatewayESP32.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP32OTA/GatewayESP32OTA.ino' && @@ -157,6 +160,7 @@ def buildArduinoMega(config, sketches, String key) { for (sketch = 0; sketch < sketches.size(); sketch++) { if (sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266SecureMQTTClient/GatewayESP8266SecureMQTTClient.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP32/GatewayESP32.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP32OTA/GatewayESP32OTA.ino' && @@ -191,6 +195,7 @@ def buildSTM32F1(config, sketches, String key) { for (sketch = 0; sketch < sketches.size(); sketch++) { if (sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266SecureMQTTClient/GatewayESP8266SecureMQTTClient.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP32/GatewayESP32.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP32OTA/GatewayESP32OTA.ino' && @@ -280,6 +285,7 @@ def buildESP32(config, sketches, String key) { sketches[sketch].path != config.library_root+'examples/GatewayGSMMQTTClient/GatewayGSMMQTTClient.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266SecureMQTTClient/GatewayESP8266SecureMQTTClient.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && sketches[sketch].path != config.library_root+'examples/SensebenderGatewaySerial/SensebenderGatewaySerial.ino' && sketches[sketch].path != config.library_root+'examples/MotionSensorRS485/MotionSensorRS485.ino' && @@ -316,6 +322,7 @@ def buildnRF5(config, sketches, String key) { sketches[sketch].path != config.library_root+'examples/DustSensorDSM/DustSensorDSM.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266SecureMQTTClient/GatewayESP8266SecureMQTTClient.ino' && sketches[sketch].path != config.library_root+'examples/GatewayGSMMQTTClient/GatewayGSMMQTTClient.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && sketches[sketch].path != config.library_root+'examples/GatewayESP32/GatewayESP32.ino' && @@ -396,4 +403,4 @@ def buildnRF51822(config, sketches, String key) { } } -return this \ No newline at end of file +return this diff --git a/MyConfig.h b/MyConfig.h index c519f2745..7811dc9af 100755 --- a/MyConfig.h +++ b/MyConfig.h @@ -1426,6 +1426,8 @@ * @brief Define this for Ethernet GW based on the ENC28J60 module. * @def MY_GATEWAY_ESP8266 * @brief Define this for Ethernet GW based on the ESP8266. + * @def MY_GATEWAY_ESP8266_SECURE + * @brief Define this for Ethernet GW based on the ESP8266 with TLS. * @def MY_GATEWAY_ESP32 * @brief Define this for Ethernet GW based on the ESP32. * @def MY_GATEWAY_LINUX @@ -1441,6 +1443,7 @@ //#define MY_GATEWAY_W5100 //#define MY_GATEWAY_ENC28J60 //#define MY_GATEWAY_ESP8266 +//#define MY_GATEWAY_ESP8266_SECURE //#define MY_GATEWAY_ESP32 //#define MY_GATEWAY_LINUX //#define MY_GATEWAY_TINYGSM @@ -1548,29 +1551,79 @@ //#define MY_MQTT_SUBSCRIBE_TOPIC_PREFIX "mygateway1-in" /** - * @def MY_MQTT_CA_CERT - * @brief Set a specific CA certificate needed to validate MQTT server against. Use the certificate as a trust anchor, accepting remote certificates signed by it. + * @def MY_MQTT_CA_CERT1 + * @brief Up to three root Certificates Authorities could be defined to validate the mqtt server' certificate. The most secure. + * + * This define is mandatory when you need connect MQTT over SSL/TLS. Certificate Authorities. + * The best method to validate server certificates. + * Advised to retrieve root Certificate Authorities as they expire less often than server certificates. + * With let's encrypt you may need up to three Certificate Authorities * - * This define is mandatory when you need connect MQTT over SSL/TLS. * Example: @code * - * const char mqtt_ca_cert[] PROGMEM = R"EOF( + * const char cert_isrgrootx1_Authority[] PROGMEM = R"EOF( * ----- BEGIN THE CERTIFICATE ----- * XXX ... XXX * ----- FINISH CERTIFICATE ----- * )EOF"; * - * #define MY_MQTT_CA_CERT mqtt_ca_cert + * const char cert_isrgrootx2_Authority[] PROGMEM = R"EOF( + * ----- BEGIN THE CERTIFICATE ----- + * XXX ... XXX + * ----- FINISH CERTIFICATE ----- + * )EOF"; + * + * const char cert_letsEncryptR3_Authority[] PROGMEM = R"EOF( + * ----- BEGIN THE CERTIFICATE ----- + * XXX ... XXX + * ----- FINISH CERTIFICATE ----- + * )EOF"; + * + * #define MY_MQTT_CA_CERT1 cert_isrgrootx1_Authority + * #define MY_MQTT_CA_CERT2 cert_isrgrootx2_Authority + * #define MY_MQTT_CA_CERT3 cert_letsEncryptR3_Authority + * + * @endcode + */ +//#define MY_MQTT_CA_CERT1 + +/** + * @def MY_MQTT_CA_CERT2 + * @brief Up to three root Certificates Authorities could be defined to validate the mqtt serv. +*/ +//#define MY_MQTT_CA_CERT2 + +/** + * @def MY_MQTT_CA_CERT3 + * @brief Up to three root Certificates Authorities could be defined to validate the mqtt serv. +*/ +//#define MY_MQTT_CA_CERT3 + + +/** + * @def MY_MQTT_FINGERPRINT + * @brief Server certificate validation with its fingerprint + * + * The finger print to validate the mqtt server certificate. This is less secure and less convenient + * than using certificate authorities. + * Command (3 lines...) to obtain the certificate finger print: + * @code + * $>openssl s_client -connect : < /dev/null 2>/dev/null | \ + * openssl x509 -fingerprint -noout -in /dev/stdin \ + * awk -F= '{print $2}' + * @endcode * + * Example: @code + * const char mqtt_fingerprint [] PROGMEM = "CA:CE:2B:MD:D3:32:A3:F1:8C:73:9E:1B:B7:D5:75:4A:10:61:E4:05"; * @endcode */ -//#define MY_MQTT_CA_CERT +//#define MY_MQTT_FINGERPRINT /** * @def MY_MQTT_CLIENT_CERT * @brief Set a client certificate to send to a MQTT server that requests one over TLS connection. * - * This define is mandatory when you need connect MQTT over SSL/TLS. + * This define is mandatory when you need connect MQTT over SSL/TLS and client certificate is requested. * Example: @code * * const char mqtt_client_cert[] PROGMEM = R"EOF( @@ -1587,9 +1640,9 @@ /** * @def MY_MQTT_CLIENT_KEY - * @brief Set a client private key to send to a MQTT server that requests one over TLS connection. + * @brief Set the client private key generated with the MY_MQTT_CLIENT_CERT. * - * This define is mandatory when you need connect MQTT over SSL/TLS. + * This define is mandatory when you need connect MQTT over SSL/TLS and client certificate is requested. * Example: @code * * const char mqtt_client_key[] PROGMEM = R"EOF( @@ -2373,7 +2426,10 @@ #define MY_MQTT_CLIENT_ID #define MY_MQTT_PUBLISH_TOPIC_PREFIX #define MY_MQTT_SUBSCRIBE_TOPIC_PREFIX -#define MY_MQTT_CA_CERT +#define MY_MQTT_CA_CERT1 +#define MY_MQTT_CA_CERT2 +#define MY_MQTT_CA_CERT3 +#define MY_MQTT_FINGERPRINT #define MY_MQTT_CLIENT_CERT #define MY_MQTT_CLIENT_KEY #define MY_SIGNAL_REPORT_ENABLED diff --git a/core/MyGatewayTransportMQTTClient.cpp b/core/MyGatewayTransportMQTTClient.cpp old mode 100755 new mode 100644 index bd5ab5f58..2bd14e0d2 --- a/core/MyGatewayTransportMQTTClient.cpp +++ b/core/MyGatewayTransportMQTTClient.cpp @@ -17,6 +17,29 @@ * version 2 as published by the Free Software Foundation. */ +/* + * Modified by Eric Grammatico + * + * Added support to secured connexion to mqtt server thanks to WiFiClientSecure class. + * Please see comments in code. You can look for WiFiClientSecure, MY_GATEWAY_ESP8266_SECURE, + * MY_MQTT_CA_CERT, MY_MQTT_FINGERPRINT and MY_MQTT_CLIENT_CERT in the code below to see what has + * changed. No new method, no new class to be used by my_sensors. + * + * The following constants have to be defined from the gateway code: + * MY_GATEWAY_ESP8266_SECURE in place of MY_GATEWAY_ESP8266 to go to secure connexions. + * MY_MQTT_CA_CERTx Up to three root Certificates Authorities could be defined + * to validate the mqtt server' certificate. The most secure. + * MY_MQTT_FINGERPRINT Alternatively, the mqtt server' certificate finger print + * could be used. Less secure and less convenient as you'll + * have to update the fingerprint each time the mqtt server' + * certificate is updated + * If neither MY_MQTT_CA_CERT1 nor MY_MQTT_FINGERPRINT are + * defined, insecure connexion will be established. The mqtt + * server' certificate will not be validated. + * MY_MQTT_CLIENT_CERT The mqtt server may require client certificate for + * MY_MQTT_CLIENT_KEY authentication. + * + */ // Topic structure: MY_MQTT_PUBLISH_TOPIC_PREFIX/NODE-ID/SENSOR-ID/CMD-TYPE/ACK-FLAG/SUB-TYPE @@ -47,6 +70,12 @@ #undef MY_ESP8266_HOSTNAME // cleanup #endif +#ifdef MY_MQTT_CA_CERT +#warning MY_MQTT_CA_CERT is deprecated, please use MY_MQTT_CA_CERT1 instead! +#define MY_MQTT_CA_CERT1 MY_MQTT_CA_CERT +//#undef MY_MQTT_CA_CERT // cleanup +#endif + #ifndef MY_MQTT_USER #define MY_MQTT_USER NULL #endif @@ -55,7 +84,7 @@ #define MY_MQTT_PASSWORD NULL #endif -#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32) +#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP8266_SECURE) || defined(MY_GATEWAY_ESP32) #if !defined(MY_WIFI_SSID) #error ESP8266/ESP32 MQTT gateway: MY_WIFI_SSID not defined! #endif @@ -69,7 +98,7 @@ #define _MQTT_clientIp IPAddress(MY_IP_ADDRESS) #if defined(MY_IP_GATEWAY_ADDRESS) #define _gatewayIp IPAddress(MY_IP_GATEWAY_ADDRESS) -#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32) +#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP8266_SECURE) || defined(MY_GATEWAY_ESP32) // Assume the gateway will be the machine on the same network as the local IP // but with last octet being '1' #define _gatewayIp IPAddress(_MQTT_clientIp[0], _MQTT_clientIp[1], _MQTT_clientIp[2], 1) @@ -77,20 +106,42 @@ #if defined(MY_IP_SUBNET_ADDRESS) #define _subnetIp IPAddress(MY_IP_SUBNET_ADDRESS) -#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32) +#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP8266_SECURE) || defined(MY_GATEWAY_ESP32) #define _subnetIp IPAddress(255, 255, 255, 0) #endif /* End of MY_IP_SUBNET_ADDRESS */ #endif /* End of MY_IP_ADDRESS */ #if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32) -#if defined(MY_MQTT_CA_CERT) && defined(MY_MQTT_CLIENT_CERT) && defined(MY_MQTT_CLIENT_KEY) -#define EthernetClient WiFiClientSecure -BearSSL::X509List ca_cert(MY_MQTT_CA_CERT); -BearSSL::X509List client_cert(MY_MQTT_CLIENT_CERT); -BearSSL::PrivateKey client_key(MY_MQTT_CLIENT_KEY); -#else #define EthernetClient WiFiClient -#endif /* End of MY_MQTT_CA_CERT && MY_MQTT_CLIENT_CERT && MY_MQTT_CLIENT_KEY */ +#elif defined(MY_GATEWAY_ESP8266_SECURE) +#define EthernetClient WiFiClientSecure +#if defined(MY_MQTT_CA_CERT1) +BearSSL::X509List certAuth; //List to store Certificat Authorities +#endif +#if defined(MY_MQTT_CLIENT_CERT) && defined(MY_MQTT_CLIENT_KEY) +BearSSL::X509List clientCert; //Client public key +BearSSL::PrivateKey clientPrivKey; //Client private key +#endif +// Set time via NTP, as required for x.509 validation +// BearSSL checks NotBefore and NotAfter dates in certificates +// Thus an approximated date/time is needed. +void setClock() +{ + configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov"); + + Serial.print("Waiting for NTP time sync: "); + time_t now = time(nullptr); + while (now < 8 * 3600 * 2) { + delay(500); + Serial.print("."); + now = time(nullptr); + } + Serial.println(""); + struct tm timeinfo; + gmtime_r(&now, &timeinfo); + Serial.print("Current time: "); + Serial.print(asctime(&timeinfo)); +} #elif defined(MY_GATEWAY_LINUX) // Nothing to do here #else @@ -146,6 +197,11 @@ bool reconnectMQTT(void) { GATEWAY_DEBUG(PSTR("GWT:RMQ:CONNECTING...\n")); +#if defined(MY_GATEWAY_ESP8266_SECURE) + // Date/time are retrieved to be able to validate certificates. + setClock(); +#endif + // Attempt to connect if (_MQTT_client.connect(MY_MQTT_CLIENT_ID, MY_MQTT_USER, MY_MQTT_PASSWORD)) { GATEWAY_DEBUG(PSTR("GWT:RMQ:OK\n")); @@ -161,18 +217,24 @@ bool reconnectMQTT(void) } delay(1000); GATEWAY_DEBUG(PSTR("!GWT:RMQ:FAIL\n")); +#if defined(MY_GATEWAY_ESP8266_SECURE) + char sslErr[256]; + int errID = _MQTT_ethClient.getLastSSLError(sslErr, sizeof(sslErr)); + GATEWAY_DEBUG(PSTR("!GWT:RMQ:(%d) %s\n"), errID, sslErr); +#endif return false; } bool gatewayTransportConnect(void) { -#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32) +#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP8266_SECURE) || defined(MY_GATEWAY_ESP32) if (WiFi.status() != WL_CONNECTED) { GATEWAY_DEBUG(PSTR("GWT:TPC:CONNECTING...\n")); delay(1000); return false; } GATEWAY_DEBUG(PSTR("GWT:TPC:IP=%s\n"), WiFi.localIP().toString().c_str()); + #elif defined(MY_GATEWAY_LINUX) #if defined(MY_IP_ADDRESS) _MQTT_ethClient.bind(_MQTT_clientIp); @@ -250,10 +312,10 @@ bool gatewayTransportInit(void) _MQTT_client.setCallback(incomingMQTT); -#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32) +#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP8266_SECURE) || defined(MY_GATEWAY_ESP32) // Turn off access point WiFi.mode(WIFI_STA); -#if defined(MY_GATEWAY_ESP8266) +#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP8266_SECURE) WiFi.hostname(MY_HOSTNAME); #elif defined(MY_GATEWAY_ESP32) WiFi.setHostname(MY_HOSTNAME); @@ -264,10 +326,36 @@ bool gatewayTransportInit(void) (void)WiFi.begin(MY_WIFI_SSID, MY_WIFI_PASSWORD, 0, MY_WIFI_BSSID); #endif -#if defined(MY_MQTT_CA_CERT) && defined(MY_MQTT_CLIENT_CERT) && defined(MY_MQTT_CLIENT_KEY) - _MQTT_ethClient.setTrustAnchors(&ca_cert); - _MQTT_ethClient.setClientRSACert(&client_cert, &client_key); -#endif /* End of MY_MQTT_CA_CERT && MY_MQTT_CLIENT_CERT && MY_MQTT_CLIENT_KEY */ +#if defined(MY_GATEWAY_ESP8266_SECURE) + // Certificate Authorities are stored in the X509 list + // At least one is needed, but you may need two, or three + // eg to validate one certificate from LetsEncrypt two is needed +#if defined(MY_MQTT_CA_CERT1) + certAuth.append(MY_MQTT_CA_CERT1); +#if defined(MY_MQTT_CA_CERT2) + certAuth.append(MY_MQTT_CA_CERT2); +#endif +#if defined(MY_MQTT_CA_CERT3) + certAuth.append(MY_MQTT_CA_CERT3); +#endif + _MQTT_ethClient.setTrustAnchors(&certAuth); +#elif defined(MY_MQTT_FINGERPRINT) //MY_MQTT_CA_CERT1 + // Alternatively, the certificate could be validated with its + // fingerprint, which is less secure + _MQTT_ethClient.setFingerprint(MY_MQTT_FINGERPRINT); +#else //MY_MQTT_CA_CERT1 + // At last, an insecure connexion is accepted. Meaning the + // server's certificate is not validated. + _MQTT_ethClient.setInsecure(); + GATEWAY_DEBUG(PSTR("GWT:TPC:CONNECTING WITH INSECURE SETTING...\n")); +#endif //MY_MQTT_CA_CERT1 +#if defined(MY_MQTT_CLIENT_CERT) && defined(MY_MQTT_CLIENT_KEY) + // The server may required client certificate + clientCert.append(MY_MQTT_CLIENT_CERT); + clientPrivKey.parse(MY_MQTT_CLIENT_KEY); + _MQTT_ethClient.setClientRSACert(&clientCert, &clientPrivKey); +#endif +#endif //MY_GATEWAY_ESP8266_SECURE gatewayTransportConnect(); @@ -280,7 +368,7 @@ bool gatewayTransportAvailable(void) if (_MQTT_connecting) { return false; } -#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32) +#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP8266_SECURE) || defined(MY_GATEWAY_ESP32) if (WiFi.status() != WL_CONNECTED) { #if defined(MY_GATEWAY_ESP32) (void)gatewayTransportInit(); diff --git a/examples/GatewayESP8266SecureMQTTClient/GatewayESP8266SecureMQTTClient.ino b/examples/GatewayESP8266SecureMQTTClient/GatewayESP8266SecureMQTTClient.ino new file mode 100644 index 000000000..531adb57d --- /dev/null +++ b/examples/GatewayESP8266SecureMQTTClient/GatewayESP8266SecureMQTTClient.ino @@ -0,0 +1,190 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2019 Sensnology AB + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + ******************************* + * + * REVISION HISTORY + * Version 1.0 - Henrik Ekblad + * + * DESCRIPTION + * The ESP8266 MQTT gateway sends radio network (or locally attached sensors) data to your MQTT broker. + * The node also listens to MY_MQTT_TOPIC_PREFIX and sends out those messages to the radio network + * + * LED purposes: + * - To use the feature, uncomment any of the MY_DEFAULT_xx_LED_PINs in your sketch + * - RX (green) - blink fast on radio message received. In inclusion mode will blink fast only on presentation received + * - TX (yellow) - blink fast on radio message transmitted. In inclusion mode will blink slowly + * - ERR (red) - fast blink on error during transmission error or receive crc error + * + * See https://www.mysensors.org/build/connect_radio for wiring instructions. + * + * If you are using a "barebone" ESP8266, see + * https://www.mysensors.org/build/esp8266_gateway#wiring-for-barebone-esp8266 + * + * Inclusion mode button: + * - Connect GPIO5 (=D1) via switch to GND ('inclusion switch') + * + * Hardware SHA204 signing is currently not supported! + * + * Make sure to fill in your ssid and WiFi password below for ssid & pass. + * + ******************************** + * + * SSL support by Eric Grammatico. You should have an updated version of MyGatewayTransportMQTTClient.cpp. + * Please see: https://forum.mysensors.org/topic/11941/esp8266-mqtt-gateway-ssl-connection + * + * The following constants have to be defined from the gateway code: + * MY_GATEWAY_ESP8266_SECURE In place of MY_GATEWAY_ESP8266 to go to secure connexions. + * MY_MQTT_CA_CERTx Up to three root Certificates Authorities could be defined + * to validate the mqtt server' certificate. The most secure. + * MY_MQTT_CA_CERT is deprecated and MY_MQTT_CA_CERT1 should + * be used instead. + * MY_MQTT_FINGERPRINT Alternatively, the mqtt server' certificate finger print + * could be used. Less secure and less convenient as you'll + * have to update the fingerprint each time the mqtt server' + * certificate is updated + * If neither MY_MQTT_CA_CERT1 nor MY_MQTT_FINGERPRINT are + * defined, insecure connexion will be established. The mqtt + * server' certificate will not be validated. + * MY_MQTT_CLIENT_CERT The mqtt server may require client certificate for + * MY_MQTT_CLIENT_KEY authentication. + * + * The certs.h file holds the mqtt server' fingerprint and root Certificate Authorities and + * client certificate and key. This a sample how to populate MY_MQTT_CA_CERTx, MY_MQTT_FINGERPRINT, + * MY_MQTT_CLIENT_CERT and MY_MQTT_CLIENT_KEY. + */ + +// Imports certificates and client key +#include "certs.h" + +/********************************** + * MySensors node configuration + */ + +// General settings +#define SKETCH_NAME "MySensorsMQTTGW_Secure" +#define SKETCH_VERSION "0.6" +#define MY_DEBUG +#define MY_NODE_ID 1 + +// Use a bit lower baudrate for serial prints on ESP8266 than default in MyConfig.h +#define MY_BAUD_RATE 9600 + +// Enables and select radio type (if attached) +#define MY_RADIO_RF24 +//#define MY_RF24_PA_LEVEL RF24_PA_LOW + +//#define MY_RADIO_RFM69 +//#define MY_RADIO_RFM95 + +/************** + * Secured connexion with ESP8266 + */ +#define MY_GATEWAY_ESP8266_SECURE +//** Set WIFI SSID and password +#define MY_WIFI_SSID "ssid" +#define MY_WIFI_PASSWORD "password" +//** Set the hostname for the WiFi Client. This is the hostname +// passed to the DHCP server if not static. +#define MY_HOSTNAME "esp8266-gw" +// Enable MY_IP_ADDRESS here if you want a static ip address (no DHCP) +//#define MY_IP_ADDRESS 192,168,178,87 + +// If using static ip you can define Gateway and Subnet address as well +//#define MY_IP_GATEWAY_ADDRESS 192,168,178,1 +//#define MY_IP_SUBNET_ADDRESS 255,255,255,0 + +//** Certificate Authorities. One or two should be enough +#define MY_MQTT_CA_CERT1 cert_isrgrootx1_Authority +#define MY_MQTT_CA_CERT2 cert_isrgrootx2_Authority +//#define MY_MQTT_CA_CERT3 cert_letsEncryptR3_Authority + +//** Server certificate validation with its fingerprint +// less secure and less convenient than with Certificate +// Authorities as server certificates are updated often. +// Will not be used if MY_MQTT_CA_CERT1 defined. +#define MY_MQTT_FINGERPRINT mqtt_fingerprint + +//** The mqtt server may require client certificate for +// authentication. +#define MY_MQTT_CLIENT_CERT cert_client +#define MY_MQTT_CLIENT_KEY key_client + + +/************** + * MQTT_CLIENT configuration + */ +#define MY_GATEWAY_MQTT_CLIENT + +//** MQTT broker if using URL instead of ip address. +// should correspond to the CN field in the mqtt server' +// certificate. +#define MY_CONTROLLER_URL_ADDRESS mqtt_host + +//** The MQTT broker port to open +#define MY_PORT mqtt_port + +//** Enable these if your MQTT broker requires username/password +//#define MY_MQTT_USER "" +//#define MY_MQTT_PASSWORD "" +//** Set MQTT client id +//#define MY_MQTT_CLIENT_ID "" + +//** Set this node's subscribe and publish topic prefix +#define MY_MQTT_PUBLISH_TOPIC_PREFIX "esp8266-gw/out" +#define MY_MQTT_SUBSCRIBE_TOPIC_PREFIX "esp8266-gw/in" + + +// Enable inclusion mode +//#define MY_INCLUSION_MODE_FEATURE +// Enable Inclusion mode button on gateway +//#define MY_INCLUSION_BUTTON_FEATURE +// Set inclusion mode duration (in seconds) +//#define MY_INCLUSION_MODE_DURATION 60 +// Digital pin used for inclusion mode button +//#define MY_INCLUSION_MODE_BUTTON_PIN D1 + +// Set blinking period +//#define MY_DEFAULT_LED_BLINK_PERIOD 300 + +// Flash leds on rx/tx/err +//#define MY_DEFAULT_ERR_LED_PIN 16 // Error led pin +//#define MY_DEFAULT_RX_LED_PIN 16 // Receive led pin +//#define MY_DEFAULT_TX_LED_PIN 16 // the PCB, on board LED + +#include + +void setup() +{ + + // In order to speed up certs and keys verifications + system_update_cpu_freq(160); + + // Setup locally attached sensors +} + +void presentation() +{ + // Present locally attached sensors here +} + +void loop() +{ + // Send locally attached sensors data here +} + diff --git a/examples/GatewayESP8266SecureMQTTClient/certs.h b/examples/GatewayESP8266SecureMQTTClient/certs.h new file mode 100644 index 000000000..443fd2821 --- /dev/null +++ b/examples/GatewayESP8266SecureMQTTClient/certs.h @@ -0,0 +1,148 @@ +#pragma once + +// The mqqt host and port +const char* mqtt_host = ""; +const uint16_t mqtt_port = 8883; //Should be your mqtt broker' port + +/* +*The finger print to validate the mqtt server certificate. This is less secure and less convenient +* than using certificate authorities +* Command (3 lines...) to obtain the certificate finger print: +* $>openssl s_client -connect : < /dev/null 2>/dev/null | \ +* openssl x509 -fingerprint -noout -in /dev/stdin \ +* awk -F= '{print $2}' +*/ +const char mqtt_fingerprint [] PROGMEM = + "CA:EE:2B:ED:D3:23:A7:F1:8C:73:9E:9B:B7:D5:75:41:10:61:E4:05"; + +/// @cond RAW_LITERALS +// Doxygen doesn't seem to like raw literals + +/* +*Certificate Authorities. The best method to validate server certificates +* Advised to retrieve root Certificate Authorities as they expire less often +* than server certificates. Here after letsencrypt Certificate Authorities are listed. +* They are available at https://letsencrypt.org/certificates/ +* +* Root Certificate Authorities ISRG Root X1 +* https://letsencrypt.org/certs/isrgrootx1.pem +* Not Before: Jun 4 11:04:38 2015 GMT +* Not After : Jun 4 11:04:38 2035 GMT +*/ +const char cert_isrgrootx1_Authority [] PROGMEM = R"CERT( +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- +)CERT"; + +/* +* Root Certificate Authorities ISRG Root X2 +* https://letsencrypt.org/certs/isrg-root-x2.pem +* Not Before: Sep 4 00:00:00 2020 GMT +* Not After : Sep 17 16:00:00 2040 GMT +*/ +const char cert_isrgrootx2_Authority [] PROGMEM = R"CERT( +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- +)CERT"; + +/* +* This one shouldn't be needed.... +* Root Certificate Authorities Let’s Encrypt R3 +* https://letsencrypt.org/certs/lets-encrypt-r3.pem +* Not Before: Sep 4 00:00:00 2020 GMT +* Not After : Sep 15 16:00:00 2025 GMT +*/ +const char cert_letsEncryptR3_Authority [] PROGMEM = R"CERT( +-----BEGIN CERTIFICATE----- +MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw +WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP +R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx +sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm +NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg +Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG +/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB +Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA +FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw +AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw +Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB +gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W +PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl +ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz +CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm +lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 +avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 +yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O +yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids +hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ +HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv +MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX +nLRbwHOoq7hHwg== +-----END CERTIFICATE----- +)CERT"; + +/* +*The mqtt server may require client certificate for authentication. +* The following are genereted and signed thanks to openssl. +* The signing certificate is holded by the mqtt server. +* Please see Client section in https://mosquitto.org/man/mosquitto-tls-7.html +*/ +const char cert_client [] PROGMEM = R"CERT( +-----BEGIN CERTIFICATE----- +... ... ... ... ... +-----END CERTIFICATE----- +)CERT"; + +const char key_client [] PROGMEM = R"CERT( +-----BEGIN RSA PRIVATE KEY----- +... ... ... ... ... +-----END RSA PRIVATE KEY----- +)CERT"; +// end of certificate chain + +/// @endcond diff --git a/keywords.txt b/keywords.txt index d0143ba6b..0889219f1 100755 --- a/keywords.txt +++ b/keywords.txt @@ -251,16 +251,21 @@ MY_GATEWAY_CLIENT_MODE LITERAL1 MY_GATEWAY_ENC28J60 LITERAL1 MY_GATEWAY_ESP32 LITERAL1 MY_GATEWAY_ESP8266 LITERAL1 +MY_GATEWAY_ESP8266_SECURE LITERAL1 MY_GATEWAY_MQTT_CLIENT LITERAL1 MY_GATEWAY_SERIAL LITERAL1 MY_GATEWAY_W5100 LITERAL1 MY_HOSTNAME LITERAL1 MY_INCLUSION_BUTTON_EXTERNAL_PULLUP LITERAL1 -MY_MQTT_CA_CERT LITERAL1 +MY_MQTT_CA_CERT LITERAL1 +MY_MQTT_CA_CERT1 LITERAL1 +MY_MQTT_CA_CERT2 LITERAL1 +MY_MQTT_CA_CERT3 LITERAL1 MY_MQTT_CLIENT_CERT LITERAL1 MY_MQTT_CLIENT_ID LITERAL1 MY_MQTT_CLIENT_KEY LITERAL1 MY_MQTT_CLIENT_PUBLISH_RETAIN LITERAL1 +MY_MQTT_FINGERPRINT LITERAL1 MY_MQTT_PASSWORD LITERAL1 MY_MQTT_PUBLISH_TOPIC_PREFIX LITERAL1 MY_MQTT_SUBSCRIBE_TOPIC_PREFIX LITERAL1