From e5629c9b0478ad06d2e75ed0f869fa3839a2cacb Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sun, 11 Sep 2016 01:46:42 +0200 Subject: [PATCH] Initial TLS support --- README.md | 13 +++++++---- common/common_config.h | 7 +++++- master/.gitignore | 2 ++ master/README.md | 9 ++++++++ master/{ota.py => client.py} | 9 +++++--- master/docker-compose.yml | 8 +++++-- master/mqtt/config/tls.conf | 4 ++++ spejsiot/SpejsNode.cpp | 45 +++++++++++++++++++----------------- spejsiot/SpejsNode.h | 2 +- switch/Makefile-user.mk | 7 +++--- switch/app/application.cpp | 2 +- switch/rom0.ld | 3 ++- 12 files changed, 74 insertions(+), 37 deletions(-) create mode 100644 master/.gitignore create mode 100644 master/README.md rename master/{ota.py => client.py} (88%) create mode 100644 master/mqtt/config/tls.conf diff --git a/README.md b/README.md index 710cdb4..4bc8191 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,14 @@ is directly connected on ESP-12E module. Software -------- -Mosquitto contained in docker is used as a broker. ESP8266 code uses [Sming -framework](https://github.com/SmingHub/Sming). Set your Wifi configuration in -`common/common_config.h` (used by both `switch` and `light`) +Mosquitto contained in docker is used as a broker. For proper TLS you need to +create self-signed certificate and store its SHA-1 fingerprint in +`common/common_config.h`. + +ESP8266 code uses [slaff's +fork of Sming framework](https://github.com/slaff/Sming). (For axTLS support) +Set your Wifi configuration in `common/common_config.h` (used by both `switch` +and `light`) Thoughts @@ -27,4 +32,4 @@ Oh my, that's slow.^W^Wquite fast, when patched properly. **WARNING!** `Sming.reset();` jumps to (serial) bootloader right after flashing. This causes OTA to fail with `wdt reset`. External RESET assert is required after -flashing. \ No newline at end of file +flashing. diff --git a/common/common_config.h b/common/common_config.h index d603f30..686dd85 100644 --- a/common/common_config.h +++ b/common/common_config.h @@ -8,7 +8,12 @@ #ifndef MQTT_BROKER #define MQTT_BROKER "192.168.0.100" - #define MQTT_PORT 1883 + #ifdef ENABLE_SSL + #define MQTT_PORT 8883 + #define SSL_FINGERPRINT { } // TODO + #else + #define MQTT_PORT 1883 + #endif #endif #define TOPIC_PREFIX "iot/" diff --git a/master/.gitignore b/master/.gitignore new file mode 100644 index 0000000..24b14fc --- /dev/null +++ b/master/.gitignore @@ -0,0 +1,2 @@ +ota/* +mqtt/certs/* diff --git a/master/README.md b/master/README.md new file mode 100644 index 0000000..f5b19a5 --- /dev/null +++ b/master/README.md @@ -0,0 +1,9 @@ +``` +openssl req -nodes -new -x509 -days 3650 -extensions v3_ca -keyout ca.key -out ca.crt +openssl genrsa -out server.key 2048 +openssl req -out server.csr -key server.key -new +openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650 + +# Extract SHA-1 +openssl x509 -in server.crt -sha1 -noout -fingerprint | sed 's/.*=/0x/g; s/:/, 0x/g' +``` diff --git a/master/ota.py b/master/client.py similarity index 88% rename from master/ota.py rename to master/client.py index cea70e7..4e96b10 100644 --- a/master/ota.py +++ b/master/client.py @@ -6,18 +6,21 @@ import sys # The callback for when the client receives a CONNACK response from the server. def on_connect(client, userdata, rc): print("Connected with result code "+str(rc)) + # Subscribing in on_connect() means that if we lose the connection and # reconnect then subscriptions will be renewed. - client.subscribe("light/status") client.subscribe("#") client.publish("iot/" + sys.argv[1] + "/" + sys.argv[2], sys.argv[3]) # The callback for when a PUBLISH message is received from the server. def on_message(client, userdata, msg): print(str(time.time())+" "+msg.topic+" "+str(msg.payload)) - exit() -client = mqtt.Client("test-client-%d" % random.randint(100, 999)) +if len(sys.argv) < 4: + print('Usage: client.py [DEVICE_ID] [ENDPOINT] [VALUE]'); + exit(1) + +client = mqtt.Client() client.on_connect = on_connect client.on_message = on_message diff --git a/master/docker-compose.yml b/master/docker-compose.yml index 3722e05..b0ebf34 100644 --- a/master/docker-compose.yml +++ b/master/docker-compose.yml @@ -1,11 +1,15 @@ mosquitto: image: toke/mosquitto + volumes: + - mqtt/config/:/mqtt/config/conf.d:ro + - mqtt/certs/:/mqtt/config/certs:ro ports: - "1883:1883" + - "8883:8883" nginx: image: nginx volumes: - - ota:/usr/share/nginx/html + - ota:/usr/share/nginx/html ports: - - "80:80" + - "80:80" diff --git a/master/mqtt/config/tls.conf b/master/mqtt/config/tls.conf new file mode 100644 index 0000000..4d482ab --- /dev/null +++ b/master/mqtt/config/tls.conf @@ -0,0 +1,4 @@ +listener 8883 +cafile /mqtt/config/certs/ca.crt +certfile /mqtt/config/certs/server.crt +keyfile /mqtt/config/certs/server.key diff --git a/spejsiot/SpejsNode.cpp b/spejsiot/SpejsNode.cpp index b72308b..e383548 100644 --- a/spejsiot/SpejsNode.cpp +++ b/spejsiot/SpejsNode.cpp @@ -49,14 +49,22 @@ void SpejsNode::keepAliveHandler() { void SpejsNode::onConnected() { Serial.println("Connection successful"); - // "+deviceID+"/ + mqtt.setWill(TOPIC_PREFIX + deviceID + "/state", "offline", 1, true); + +#ifdef ENABLE_SSL + const uint8_t sha1Fingerprint[] = SSL_FINGERPRINT; + mqtt.connect("iot-" + deviceID, "", "", true); + mqtt.addSslOptions(SSL_SERVER_VERIFY_LATER); + mqtt.setSslFingerprint(sha1Fingerprint, 20); +#else mqtt.connect("iot-" + deviceID); +#endif mqtt.subscribe(TOPIC_PREFIX + deviceID + "/control"); for(unsigned int i = 0 ; i < inputs.count() ; i++) { - mqtt.subscribe(TOPIC_PREFIX + deviceID + "/" + inputs.keyAt(i)); + mqtt.subscribe(TOPIC_PREFIX + deviceID + "/" + inputs.keyAt(i)); } mqtt.publish(TOPIC_PREFIX + deviceID + "/state", "online"); @@ -80,9 +88,10 @@ void SpejsNode::registerInput(String key, MqttStringSubscriptionCallback callbac void SpejsNode::mqttCallback(String origtopic, String value) { String devicePrefix = TOPIC_PREFIX + deviceID; + if(!origtopic.startsWith(devicePrefix)) { - Serial.println("ignoring"); - return; + Serial.println("ignoring"); + return; } String topic = origtopic.substring(devicePrefix.length() + 1); @@ -91,24 +100,20 @@ void SpejsNode::mqttCallback(String origtopic, String value) { Serial.println(value); if(inputs.contains(topic)) { - Serial.println("dupa"); - inputs[topic](origtopic, value); + inputs[topic](origtopic, value); } else { - Serial.println("unknown topic?"); + Serial.println("unknown topic?"); } } void SpejsNode::controlHandler(String key, String value) { Serial.println("Control command: " + value); if(value == "ota") { - startOTA(); + startOTA(); } else if(value == "restart") { - //System.restart(); - keepaliveTimer.initializeMs(500, *[] { - System.restart(); - }).start(); + System.restart(); } else { - Serial.println("Invalid command"); + Serial.println("Invalid command"); } } @@ -127,9 +132,9 @@ void SpejsNode::startOTA() { bootconf = rboot_get_config(); if (currentSlot == 0) - slot = 1; + slot = 1; else - slot = 0; + slot = 0; Serial.printf("Updating to rom %d.\r\n", slot); @@ -147,11 +152,11 @@ void SpejsNode::startOTA() { otaUpdater->setCallback(otaUpdateDelegate(&SpejsNode::otaUpdateCallback, this)); otaUpdater->start(); + notify("ota", "started"); } -void SpejsNode::otaUpdateCallback(bool result) { - Serial.println("In callback..."); +void SpejsNode::otaUpdateCallback(rBootHttpUpdate& updater, bool result) { if(result == true) { // success notify("ota", "finished"); @@ -159,18 +164,16 @@ void SpejsNode::otaUpdateCallback(bool result) { uint8 slot; if (currentSlot == 0) - slot = 1; + slot = 1; else - slot = 0; + slot = 0; // set to boot new rom and then reboot Serial.printf("Firmware updated, rebooting to rom %d...\r\n", slot); rboot_set_temp_rom(slot); - keepaliveTimer.initializeMs(500, *[] { System.restart(); - }).start(); } else { notify("ota", "failed"); } diff --git a/spejsiot/SpejsNode.h b/spejsiot/SpejsNode.h index c089868..4242e42 100644 --- a/spejsiot/SpejsNode.h +++ b/spejsiot/SpejsNode.h @@ -32,5 +32,5 @@ public: void registerInput(String key, MqttStringSubscriptionCallback cb); void mqttCallback(String, String); void controlHandler(String, String); - void otaUpdateCallback(bool result); + void otaUpdateCallback(rBootHttpUpdate& updater, bool result); }; diff --git a/switch/Makefile-user.mk b/switch/Makefile-user.mk index 1367727..5735625 100644 --- a/switch/Makefile-user.mk +++ b/switch/Makefile-user.mk @@ -1,7 +1,8 @@ MODULES = app ../spejsiot DISABLE_SPIFFS = 1 USER_CFLAGS = -I../common - -RBOOT_ENABLED ?= 1 -RBOOT_BIG_FLASH ?= 1 +ENABLE_SSL = 1 +RBOOT_ENABLED = 1 +RBOOT_BIG_FLASH = 1 +RBOOT_RTC_ENABLED = 1 SPI_SIZE ?= 4M diff --git a/switch/app/application.cpp b/switch/app/application.cpp index 1a4ca53..763fb00 100644 --- a/switch/app/application.cpp +++ b/switch/app/application.cpp @@ -4,7 +4,7 @@ SpejsNode node("switch"); void init() { node.init(); - node.registerInput("relay", *[](String value) { + node.registerInput("relay", *[](String key, String value) { Serial.println("handler"); digitalWrite(5, value == "1" ? HIGH : LOW); }); diff --git a/switch/rom0.ld b/switch/rom0.ld index 7b2a9f7..32a7c8f 100644 --- a/switch/rom0.ld +++ b/switch/rom0.ld @@ -5,7 +5,7 @@ MEMORY dport0_0_seg : org = 0x3FF00000, len = 0x10 dram0_0_seg : org = 0x3FFE8000, len = 0x14000 iram1_0_seg : org = 0x40100000, len = 0x8000 - irom0_0_seg : org = 0x40202010, len = 0x42000 + irom0_0_seg : org = 0x40202010, len = 0xF0000 } PHDRS @@ -161,6 +161,7 @@ SECTIONS *(.irom0.literal .irom.literal .irom.text.literal .irom0.text .irom.text) out/build/app_app.a:*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*) *libsming.a:*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*) + *libsmingssl.a:*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*) _irom0_text_end = ABSOLUTE(.); _flash_code_end = ABSOLUTE(.); } >irom0_0_seg :irom0_0_phdr