Implemented upload of certificates for MQTT SSL

This commit is contained in:
Gunnar Skjold 2020-04-29 21:04:42 +02:00
parent ab175ec9ec
commit cc032fdf29
8 changed files with 501 additions and 7 deletions

View File

@ -160,6 +160,15 @@ void AmsConfiguration::setMqttPayloadFormat(int mqttPayloadFormat) {
this->mqttPayloadFormat = mqttPayloadFormat;
}
bool AmsConfiguration::isMqttSsl() {
return this->mqttSsl;
}
void AmsConfiguration::setMqttSsl(bool mqttSsl) {
mqttChanged |= this->mqttSsl != mqttSsl;
this->mqttSsl = mqttSsl;
}
void AmsConfiguration::clearMqtt() {
setMqttHost("");
setMqttPort(1883);
@ -278,6 +287,7 @@ bool AmsConfiguration::hasConfig() {
case 75:
case 80:
case 81:
case 82:
return true;
default:
configVersion = 0;
@ -310,6 +320,9 @@ bool AmsConfiguration::load() {
case 81:
success = loadConfig81(address);
break;
case 82:
success = loadConfig82(address);
break;
}
EEPROM.end();
return success;
@ -608,6 +621,101 @@ bool AmsConfiguration::loadConfig81(int address) {
return true;
}
bool AmsConfiguration::loadConfig82(int address) {
char* temp;
address += readString(address, &temp);
setWifiSsid(temp);
address += readString(address, &temp);
setWifiPassword(temp);
bool staticIp = false;
address += readBool(address, &staticIp);
if(staticIp) {
address += readString(address, &temp);
setWifiIp(temp);
address += readString(address, &temp);
setWifiGw(temp);
address += readString(address, &temp);
setWifiSubnet(temp);
address += readString(address, &temp);
setWifiDns1(temp);
address += readString(address, &temp);
setWifiDns2(temp);
}
address += readString(address, &temp);
setWifiHostname(temp);
bool mqtt = false;
address += readBool(address, &mqtt);
if(mqtt) {
address += readString(address, &temp);
setMqttHost(temp);
int port;
address += readInt(address, &port);
setMqttPort(port);
address += readString(address, &temp);
setMqttClientId(temp);
address += readString(address, &temp);
setMqttPublishTopic(temp);
address += readString(address, &temp);
setMqttSubscribeTopic(temp);
bool secure = false;
address += readBool(address, &secure);
if (secure)
{
address += readString(address, &temp);
setMqttUser(temp);
address += readString(address, &temp);
setMqttPassword(temp);
} else {
setMqttUser("");
setMqttPassword("");
}
int payloadFormat;
address += readInt(address, &payloadFormat);
setMqttPayloadFormat(payloadFormat);
bool ssl = false;
address += readBool(address, &ssl);
setMqttSsl(ssl);
} else {
clearMqtt();
}
address += readByte(address, &authSecurity);
if (authSecurity > 0) {
address += readString(address, &temp);
setAuthUser(temp);
address += readString(address, &temp);
setAuthPassword(temp);
} else {
clearAuth();
}
int i;
address += readInt(address, &i);
setMeterType(i);
address += readInt(address, &i);
setDistributionSystem(i);
address += readInt(address, &i);
setMainFuse(i);
address += readInt(address, &i);
setProductionCapacity(i);
bool debugTelnet = false;
address += readBool(address, &debugTelnet);
setDebugTelnet(debugTelnet);
bool debugSerial = false;
address += readBool(address, &debugSerial);
setDebugSerial(debugSerial);
address += readInt(address, &i);
setDebugLevel(i);
ackWifiChange();
return true;
}
bool AmsConfiguration::save() {
int address = EEPROM_CONFIG_ADDRESS;
@ -643,6 +751,7 @@ bool AmsConfiguration::save() {
address += saveBool(address, false);
}
address += saveInt(address, mqttPayloadFormat);
address += saveBool(address, mqttSsl);
} else {
address += saveBool(address, false);
}

View File

@ -48,6 +48,8 @@ public:
void setMqttPassword(String mqttPassword);
int getMqttPayloadFormat();
void setMqttPayloadFormat(int mqttPayloadFormat);
bool isMqttSsl();
void setMqttSsl(bool mqttSsl);
void clearMqtt();
bool isMqttChanged();
@ -102,6 +104,7 @@ private:
String mqttUser;
String mqttPassword;
int mqttPayloadFormat = 0;
bool mqttSsl;
bool mqttChanged = false;
byte authSecurity;
@ -114,13 +117,14 @@ private:
int debugLevel = 3;
const int EEPROM_SIZE = 512;
const int EEPROM_CHECK_SUM = 81; // Used to check if config is stored. Change if structure changes
const int EEPROM_CHECK_SUM = 82; // Used to check if config is stored. Change if structure changes
const int EEPROM_CONFIG_ADDRESS = 0;
bool loadConfig72(int address);
bool loadConfig75(int address);
bool loadConfig80(int address);
bool loadConfig81(int address);
bool loadConfig82(int address);
int saveString(int pAddress, const char* pString);
int readString(int pAddress, char* pString[]);

View File

@ -11,6 +11,7 @@
#include <ESP8266mDNS.h>
#elif defined(ESP32)
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <ESPmDNS.h>
#include "SPIFFS.h"
#include "Update.h"

View File

@ -52,7 +52,6 @@ RemoteDebug Debug;
AmsWebServer ws(&Debug);
WiFiClient *client;
MQTTClient mqtt(512);
HanReader hanReader;
@ -188,7 +187,6 @@ void setup() {
if(config.hasConfig()) {
if(Debug.isActive(RemoteDebug::INFO)) config.print(&Debug);
WiFi_connect();
client = new WiFiClient();
} else {
if(Debug.isActive(RemoteDebug::INFO)) {
debugI("No configuration, booting AP");
@ -635,6 +633,85 @@ void MQTT_connect() {
mqtt.disconnect();
yield();
Client *client;
if(config.isMqttSsl()) {
debugI("MQTT SSL is configured");
WiFiClientSecure *secureClient = new WiFiClientSecure();
bool spiffs = false;
#if defined(ESP32)
debugD("ESP32 SPIFFS");
spiffs = SPIFFS.begin(true);
#else
debugD("ESP8266 SPIFFS");
spiffs = SPIFFS.begin();
#endif
bool caExists = false;
if(spiffs) {
char *ca = NULL;
char *cert = NULL;
char *key = NULL;
if(SPIFFS.exists("/mqtt-ca.cer")) {
debugI("Found MQTT CA file");
File file = SPIFFS.open("/mqtt-ca.cer", "r");
ca = new char[file.size()];
if (file.size() != file.readBytes(ca, file.size())) {
delete ca;
ca = NULL;
}
}
if(SPIFFS.exists("/mqtt-cert.cer")) {
debugI("Found MQTT certificate file");
File file = SPIFFS.open("/mqtt-cert.cer", "r");
cert = new char[file.size()];
if (file.size() != file.readBytes(cert, file.size())) {
delete cert;
cert = NULL;
}
}
if(SPIFFS.exists("/mqtt-key.cer")) {
debugI("Found MQTT key file");
File file = SPIFFS.open("/mqtt-key.cer", "r");
key = new char[file.size()];
if (file.size() != file.readBytes(key, file.size())) {
delete key;
key = NULL;
}
}
SPIFFS.end();
if(ca) {
#ifdef ESP32
secureClient->setCACert(ca);
#else
secureClient->setTrustAnchors(new X509List(ca));
secureClient->allowSelfSignedCerts();
#endif
caExists = true;
}
if(cert && key) {
#ifdef ESP32
secureClient->setCertificate(cert);
secureClient->setPrivateKey(key);
#else
secureClient->setClientRSACert(new X509List(cert), new PrivateKey(key));
#endif
}
}
if(!caExists) {
debugW("No CA found, using insecure");
#ifdef ESP32
// TODO
#else
secureClient->setInsecure();
#endif
}
client = secureClient;
} else {
client = new WiFiClient();
}
mqtt.begin(config.getMqttHost().c_str(), config.getMqttPort(), *client);
// Connect to a unsecure or secure MQTT server
@ -655,6 +732,7 @@ void MQTT_connect() {
sendSystemStatusToMqtt();
}
} else {
lastMqttRetry = millis() + 30000;
if (Debug.isActive(RemoteDebug::ERROR)) {
debugI("Failed to connect to MQTT");
}

View File

@ -10,6 +10,7 @@
#include "root/restartwait_html.h"
#include "root/boot_css.h"
#include "root/gaugemeter_js.h"
#include "root/upload_html.h"
#include "Base64.h"
@ -30,13 +31,20 @@ void AmsWebServer::setup(AmsConfiguration* config, MQTTClient* mqtt) {
server.on("/gaugemeter.js", HTTP_GET, std::bind(&AmsWebServer::gaugemeterJs, this));
server.on("/data.json", HTTP_GET, std::bind(&AmsWebServer::dataJson, this));
server.on("/save", std::bind(&AmsWebServer::handleSave, this));
server.on("/save", HTTP_POST, std::bind(&AmsWebServer::handleSave, this));
server.on("/config-system", HTTP_GET, std::bind(&AmsWebServer::configSystemHtml, this));
server.on("/config-system", HTTP_POST, std::bind(&AmsWebServer::configSystemPost, this), std::bind(&AmsWebServer::configSystemUpload, this));
server.on("/config-system", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::configSystemUpload, this));
server.on("/restart-wait", HTTP_GET, std::bind(&AmsWebServer::restartWaitHtml, this));
server.on("/is-alive", HTTP_GET, std::bind(&AmsWebServer::isAliveCheck, this));
server.on("/mqtt-ca", HTTP_GET, std::bind(&AmsWebServer::mqttCa, this));
server.on("/mqtt-ca", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::mqttCaUpload, this));
server.on("/mqtt-cert", HTTP_GET, std::bind(&AmsWebServer::mqttCert, this));
server.on("/mqtt-cert", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::mqttCertUpload, this));
server.on("/mqtt-key", HTTP_GET, std::bind(&AmsWebServer::mqttKey, this));
server.on("/mqtt-key", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::mqttKeyUpload, this));
server.begin(); // Web server start
}
@ -252,6 +260,26 @@ void AmsWebServer::configMqttHtml() {
html.replace("${config.mqttPayloadFormat" + String(i) + "}", config->getMqttPayloadFormat() == i ? "selected" : "");
}
html.replace("${config.mqttSsl}", config->isMqttSsl() ? "checked" : "");
html.replace("${display.ssl}", config->isMqttSsl() ? "" : "none");
if(SPIFFS.begin()) {
html.replace("${display.ca.upload}", SPIFFS.exists("/mqtt-ca.pem") ? "none" : "");
html.replace("${display.ca.file}", SPIFFS.exists("/mqtt-ca.pem") ? "" : "none");
html.replace("${display.cert.upload}", SPIFFS.exists("/mqtt-cert.pem") ? "none" : "");
html.replace("${display.cert.file}", SPIFFS.exists("/mqtt-cert.pem") ? "" : "none");
html.replace("${display.key.upload}", SPIFFS.exists("/mqtt-key.pem") ? "none" : "");
html.replace("${display.key.file}", SPIFFS.exists("/mqtt-key.pem") ? "" : "none");
SPIFFS.end();
} else {
html.replace("${display.ca.upload}", "");
html.replace("${display.ca.file}", "none");
html.replace("${display.cert.upload}", "");
html.replace("${display.cert.file}", "none");
html.replace("${display.key.upload}", "");
html.replace("${display.key.file}", "none");
}
server.setContentLength(html.length());
server.send(200, "text/html", html);
}
@ -489,6 +517,7 @@ void AmsWebServer::handleSave() {
config->setMqttUser(server.arg("mqttUser"));
config->setMqttPassword(server.arg("mqttPassword"));
config->setMqttPayloadFormat(server.arg("mqttPayloadFormat").toInt());
config->setMqttSsl(server.arg("mqttSsl") == "true");
} else {
config->clearMqtt();
}
@ -571,7 +600,7 @@ void AmsWebServer::configSystemHtml() {
server.send(200, "text/html", html);
}
void AmsWebServer::configSystemPost() {
void AmsWebServer::uploadPost() {
server.send(200);
}
@ -651,6 +680,157 @@ void AmsWebServer::isAliveCheck() {
server.send(200);
}
void AmsWebServer::mqttCa() {
printD("Serving /mqtt-ca.html over http...");
String html = String((const __FlashStringHelper*) UPLOAD_HTML);
html.replace("${menu.meter.class}", "");
html.replace("${menu.wifi.class}", "");
html.replace("${menu.mqtt.class}", "active");
html.replace("${menu.web.class}", "");
html.replace("${menu.system.class}", "");
html.replace("${file.label}", "CA file");
server.sendHeader("Cache-Control", "public, max-age=3600");
server.setContentLength(html.length());
server.send(200, "text/html", html);
}
void AmsWebServer::mqttCaUpload() {
HTTPUpload& upload = server.upload();
if(upload.status == UPLOAD_FILE_START){
String filename = upload.filename;
if (!SPIFFS.begin()) {
printE("An Error has occurred while mounting SPIFFS");
String html = "<html><body><h1>Error uploading!</h1></form>";
server.send(500, "text/html", html);
} else {
printD("handleFileUpload Name: %s", filename.c_str());
mqttCaFile = SPIFFS.open("/mqtt-ca.pem", "w");
filename = String();
}
} else if(upload.status == UPLOAD_FILE_WRITE) {
if(mqttCaFile)
mqttCaFile.write(upload.buf, upload.currentSize);
} else if(upload.status == UPLOAD_FILE_END) {
if(mqttCaFile) {
mqttCaFile.close();
SPIFFS.end();
printD("handleFileUpload Size: %d", upload.totalSize);
server.sendHeader("Location","/config-mqtt");
server.send(303);
} else {
server.send(500, "text/plain", "500: couldn't create file");
}
}
}
void AmsWebServer::mqttCaDelete() {
}
void AmsWebServer::mqttCert() {
printD("Serving /mqtt-cert.html over http...");
String html = String((const __FlashStringHelper*) UPLOAD_HTML);
html.replace("${menu.meter.class}", "");
html.replace("${menu.wifi.class}", "");
html.replace("${menu.mqtt.class}", "active");
html.replace("${menu.web.class}", "");
html.replace("${menu.system.class}", "");
html.replace("${file.label}", "Certificate");
server.sendHeader("Cache-Control", "public, max-age=3600");
server.setContentLength(html.length());
server.send(200, "text/html", html);
}
void AmsWebServer::mqttCertUpload() {
HTTPUpload& upload = server.upload();
if(upload.status == UPLOAD_FILE_START){
String filename = upload.filename;
if (!SPIFFS.begin()) {
printE("An Error has occurred while mounting SPIFFS");
String html = "<html><body><h1>Error uploading!</h1></form>";
server.send(500, "text/html", html);
} else {
printD("handleFileUpload Name: %s", filename.c_str());
mqttCertFile = SPIFFS.open("/mqtt-cert.pem", "w");
filename = String();
}
} else if(upload.status == UPLOAD_FILE_WRITE) {
if(mqttCertFile)
mqttCertFile.write(upload.buf, upload.currentSize);
} else if(upload.status == UPLOAD_FILE_END) {
if(mqttCertFile) {
mqttCertFile.close();
SPIFFS.end();
printD("handleFileUpload Size: %d", upload.totalSize);
server.sendHeader("Location","/config-mqtt");
server.send(303);
} else {
server.send(500, "text/plain", "500: couldn't create file");
}
}
}
void AmsWebServer::mqttCertDelete() {
}
void AmsWebServer::mqttKey() {
printD("Serving /mqtt-key.html over http...");
String html = String((const __FlashStringHelper*) UPLOAD_HTML);
html.replace("${menu.meter.class}", "");
html.replace("${menu.wifi.class}", "");
html.replace("${menu.mqtt.class}", "active");
html.replace("${menu.web.class}", "");
html.replace("${menu.system.class}", "");
html.replace("${file.label}", "Private key");
server.sendHeader("Cache-Control", "public, max-age=3600");
server.setContentLength(html.length());
server.send(200, "text/html", html);
}
void AmsWebServer::mqttKeyUpload() {
HTTPUpload& upload = server.upload();
if(upload.status == UPLOAD_FILE_START){
String filename = upload.filename;
if (!SPIFFS.begin()) {
printE("An Error has occurred while mounting SPIFFS");
String html = "<html><body><h1>Error uploading!</h1></form>";
server.send(500, "text/html", html);
} else {
printD("handleFileUpload Name: %s", filename.c_str());
mqttKeyFile = SPIFFS.open("/mqtt-key.pem", "w");
filename = String();
}
} else if(upload.status == UPLOAD_FILE_WRITE) {
if(mqttKeyFile)
mqttKeyFile.write(upload.buf, upload.currentSize);
} else if(upload.status == UPLOAD_FILE_END) {
if(mqttKeyFile) {
mqttKeyFile.close();
SPIFFS.end();
printD("handleFileUpload Size: %d", upload.totalSize);
server.sendHeader("Location","/config-mqtt");
server.send(303);
} else {
server.send(500, "text/plain", "500: couldn't create file");
}
}
}
void AmsWebServer::mqttKeyDelete() {
}
void AmsWebServer::printD(String fmt, ...) {
va_list args;
va_start(args, fmt);

View File

@ -45,6 +45,9 @@ private:
AmsData data;
MQTTClient* mqtt;
File firmwareFile;
File mqttCaFile;
File mqttCertFile;
File mqttKeyFile;
bool performRestart = false;
#if defined(ESP8266)
@ -67,11 +70,21 @@ private:
void handleSave();
void configSystemHtml();
void configSystemPost();
void configSystemUpload();
void restartWaitHtml();
void isAliveCheck();
void uploadPost();
void mqttCa();
void mqttCaUpload();
void mqttCaDelete();
void mqttCert();
void mqttCertUpload();
void mqttCertDelete();
void mqttKey();
void mqttKeyUpload();
void mqttKeyDelete();
void printD(String fmt, ...);
void printI(String fmt, ...);
void printW(String fmt, ...);

View File

@ -102,6 +102,49 @@
</div>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="row form-group">
<label class="col-4">SSL</label>
<div class="col-8">
<input id="mqttSsl" type="checkbox" name="mqttSsl" value="true" ${config.mqttSsl}/>
</div>
</div>
</div>
<div class="col-md-3 mqtt-ssl-config">
<div class="row form-group">
<label class="col-4">CA</label>
<div class="col-8">
<span style="display: ${display.ca.upload};"><a href="/mqtt-ca">Upload</a></span>
<span style="display: ${display.ca.file};">
<a href="#">Delete</a>
</span>
</div>
</div>
</div>
<div class="col-md-3 mqtt-ssl-config">
<div class="row form-group">
<label class="col-4">Certificate</label>
<div class="col-8">
<span style="display: ${display.cert.upload};"><a href="/mqtt-ca">Upload</a></span>
<span style="display: ${display.cert.file};">
<a href="#">Delete</a>
</span>
</div>
</div>
</div>
<div class="col-md-3 mqtt-ssl-config">
<div class="row form-group">
<label class="col-6">Private key</label>
<div class="col-6">
<span style="display: ${display.key.upload};"><a href="/mqtt-ca">Upload</a></span>
<span style="display: ${display.key.file};">
<a href="#">Delete</a>
</span>
</div>
</div>
</div>
</div>
</div>
<hr/>
<div class="row form-group">

66
web/upload.html Normal file
View File

@ -0,0 +1,66 @@
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AMS reader - Meter configuration</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" type="text/css" href="boot.css"/>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
</head>
<body class="bg-light">
<main role="main" class="container">
<header class="navbar navbar-expand navbar-dark flex-column flex-md-row bg-purple rounded mt-2 mb-4" style="background-color: var(--purple);">
<a href="/" class=""><h6 class="navbar-brand">AMS reader <small>${version}</small></h6></a>
<div class="navbar-nav-scroll">
<ul class="navbar-nav bd-navbar-nav flex-row">
<li class="nav-item">
<a class="nav-link ${menu.meter.class}" href="/config-meter">Meter</a>
</li>
<li class="nav-item">
<a class="nav-link ${menu.wifi.class}" href="/config-wifi">WiFi</a>
</li>
<li class="nav-item">
<a class="nav-link ${menu.mqtt.class}" href="/config-mqtt">MQTT</a>
</li>
<li class="nav-item">
<a class="nav-link ${menu.web.class}" href="/config-web">Web</a>
</li>
<li class="nav-item">
<a class="nav-link ${menu.system.class}" href="/config-system">System</a>
</li>
</ul>
</div>
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
<li class="nav-item">
<a class="nav-link p-2" href="https://github.com/gskjold/AmsToMqttBridge" target="_blank" rel="noopener" aria-label="GitHub">
<svg class="d-inline-block align-text-top" style="width: 2rem; height: 2rem;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 499.36" focusable="false"><title>GitHub</title><path d="M256 0C114.64 0 0 114.61 0 256c0 113.09 73.34 209 175.08 242.9 12.8 2.35 17.47-5.56 17.47-12.34 0-6.08-.22-22.18-.35-43.54-71.2 15.49-86.2-34.34-86.2-34.34-11.64-29.57-28.42-37.45-28.42-37.45-23.27-15.84 1.73-15.55 1.73-15.55 25.69 1.81 39.21 26.38 39.21 26.38 22.84 39.12 59.92 27.82 74.5 21.27 2.33-16.54 8.94-27.82 16.25-34.22-56.84-6.43-116.6-28.43-116.6-126.49 0-27.95 10-50.8 26.35-68.69-2.63-6.48-11.42-32.5 2.51-67.75 0 0 21.49-6.88 70.4 26.24a242.65 242.65 0 0 1 128.18 0c48.87-33.13 70.33-26.24 70.33-26.24 14 35.25 5.18 61.27 2.55 67.75 16.41 17.9 26.31 40.75 26.31 68.69 0 98.35-59.85 120-116.88 126.32 9.19 7.9 17.38 23.53 17.38 47.41 0 34.22-.31 61.83-.31 70.23 0 6.85 4.61 14.81 17.6 12.31C438.72 464.97 512 369.08 512 256.02 512 114.62 397.37 0 256 0z" fill="currentColor" fill-rule="evenodd"></path></svg>
</a>
</li>
</ul>
</header>
<form method="post" enctype="multipart/form-data">
<div class="my-3 p-3 bg-white rounded shadow">
<div class="row">
<div class="col-md-4">
<div class="row form-group">
<label class="col-4">${file.label}</label>
<div class="col-8">
<input type="file" name="file"/>
</div>
</div>
</div>
</div>
</div>
<hr/>
<div class="row form-group">
<div class="col-6">
<a href="javascript:history.back();" class="btn btn-outline-secondary">Back</a>
</div>
<div class="col-6 text-right">
<button class="btn btn-primary">Upload</button>
</div>
</div>
</form>
</main>
</body>
</html>