{"id":621,"date":"2021-07-12T14:45:45","date_gmt":"2021-07-12T12:45:45","guid":{"rendered":"https:\/\/www.iot-embedded.de\/iot-2021\/?p=621"},"modified":"2021-07-13T02:57:54","modified_gmt":"2021-07-13T00:57:54","slug":"update-sicherheit-verifizierte-esp-clients","status":"publish","type":"post","link":"https:\/\/www.iot-embedded.de\/iot-2021\/beverage-monitoring\/update-sicherheit-verifizierte-esp-clients\/","title":{"rendered":"Update Sicherheit: Verifizierte ESP Clients"},"content":{"rendered":"\n<p>Stand jetzt ist die Kommunikation zwischen ESP und Update Server verschl\u00fcsselt und das Auslesen von Paketen, um unser Update Artefakt zu erhalten ist nicht mehr m\u00f6glich. Gleichzeitig k\u00f6nnen wir dank der Signatur des Artefaktes sicher sein, dass wir nur unseren und keinen fremden b\u00f6swilligen Code ausf\u00fchren. Allerdings f\u00fchren wir keinerlei Client Authentifikation durch, d.h. ein fremder Client k\u00f6nnte unseren Update Server Anfragen und w\u00fcrde unser Update Artefakt einfach zugesendet bekommen. Um dies zu verhindern, nehmen wir uns ein Beispiel am MQTT Broker bei welchem wir in einem fr\u00fcheren Blogbeitrag schon Client Authentifikation durch signierte Zertifikate konfiguriert hatten.<\/p>\n\n\n\n<p>Der Update Server sollte nur Anfragen bedienen, welche ein g\u00fcltiges Zertifikat haben und nachweisen k\u00f6nnen, dass sie Besitzer dieses Zertifikat sind. Dies hei\u00dft ein jeder ESP braucht einen private Key, sowie ein Zertifikat, welches von unserer CA ausgestellt wurde. Wir m\u00fcssen nun also:<\/p>\n\n\n\n<ul><li>Es m\u00fcssen Client Zertifikate erstellt werden: Wir k\u00f6nnen als Proof-Of-Concept wiedereinmal die Keys und Zertifikate aus dem MQTT Blogbeitrag nutzen.<\/li><li>Der Update Server muss abge\u00e4ndert werden, so dass alle Anfragen validiert werden: NodeJS in Verbindung mit ExpressJS und dem HTTPS Modul macht uns das ziemlich einfach.<\/li><li>Der ESP braucht sowohl privaten Client Key als auch das Zertifikat. Beides muss bei einer Anfrage nutzbar sein: Wie schon das Validieren des Update Server durch das Einbinden des CA Zertifikat, nutzen wir hier wieder den SecureWiFiClient, welcher passende Methoden bereitstellt.<\/li><\/ul>\n\n\n\n<p>Den Update Server muss gesagt werden, das Anfragen sich auch identifizieren m\u00fcssen. Als Basis nutzen wir den Update Server, welchen wir im letzten Blogbeitrag zu verschl\u00fcsselten Updates vorgestellt haben.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const fs = require('fs');\nconst https = require('https');\nconst express = require('express');\nconst path = require('path');\n- const app = express();\n\n+ const clientAuthMiddleware = () =&gt; (req, res, next) =&gt; {\n+   if (!req.client.authorized) {\n+     return res.status(401).send('Invalid client certificate authentication.');\n+   }\n+   return next();\n+ };\n  \n+ const app = express();\n+ app.use(clientAuthMiddleware());\n\nvar PORT = 8080;\n\n\/\/ Without middleware\napp.get('\/firmware.bin.signed', function(_req, res, next){\n\n    var options = {\n        root: path.join(__dirname)\n    };\n      \n    var fileName = 'firmware.bin.signed';\n\n    res.sendFile(fileName, options, function (err) {\n        if (err) {\n            next(err);\n        } else {\n            console.log('Sent:', fileName);\n        }\n    });\n});\n  \nhttps\n  .createServer(\n    {\n      \/\/ ...\n      cert: fs.readFileSync('..\/..\/tls-mqtt\/server\/server.crt'),\n      key: fs.readFileSync('..\/..\/tls-mqtt\/server\/server.key'),\n      + ca: fs.readFileSync('..\/..\/tls-mqtt\/ca.crt'),\n      + requestCert: true,\n      + rejectUnauthorized: false,\n      \/\/ ...\n    },\n    app\n  )\n  .listen(PORT);<\/code><\/pre>\n\n\n\n<p>Die Konfiguration des Servers wird erweitert durch das Zertifikat der CA, sowie Einstellungen zu den Anforderungen von Anfragen. Zudem wird die Logik um eine Funktion erweitert, welche jede Anfrage auf die Authentifikation des Clients befragt, um sicher gehen zu k\u00f6nnen, dass nur Anfragen welche auch ein Zertifikat ausgestellt von unserer CA, besitzen, auch ein Update Artefakt erhalten.<\/p>\n\n\n\n<p>Um zu testen, ob unser Update Server funktioniert, und auch nur verifizierte Anfragen bedient werden k\u00f6nnen wir <code>curl<\/code> in Verbindung mit den Zertifikaten aus dem MQTT-Beispiel einen GET Request an den laufenden Server schicken.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ cd ${TWO_WAY_UPDATE_SERVER}\n$ npm start\n---\n$ cd ${TLS_MQTT_EXAMPLE}\n$ cd client\n$ curl https:\/\/192.168.178.123:8080\/firmware.bin.signed --cacert ..\/..\/tls-mqtt\/ca.crt --cert client.crt --key client.key &gt; test.bin.signed\n$ xxd test.bin.signed | tail -n 2\n0003fe60: 6664 01bb 55b7 6f52 20ce 04e0 d28f eee8  fd..U.oR .......\n0003fe70: 0001 0000                                ....<\/code><\/pre>\n\n\n\n<p>Wie aus dem Beitrag zu signierten Updates bekannt, endet die Update Datei mit der Hexzahl <code>0001 000<\/code>, wir k\u00f6nnen also vermuten, dass ein Update Artefakt zur\u00fcckgegeben wurde. Nun sollten wir noch \u00fcberpr\u00fcfen, ob unautorisierte Anfragen auch bedient werden.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ curl https:\/\/192.168.178.123:8080\/firmware.bin.signed --cacert ..\/..\/tls-mqtt\/ca.crt &gt; should-not-work.txt\n  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n100    42  100    42    0     0  10725      0 --:--:-- --:--:-- --:--:-- 14000\n$ cat should-not-work.txt\nInvalid client certificate authentication.%<\/code><\/pre>\n\n\n\n<p>Somit k\u00f6nnen wir sicher gehen, dass nur Anfragen ein Update Artefakt erhalten, welche ein von uns ausgestelltes Zertifikat vorweisen k\u00f6nnen und den zugeh\u00f6rigen privaten Key besitzen.<\/p>\n\n\n\n<p>Nun m\u00fcssen wir unseren ESP als Client auch mit diesen ausstatten, sowie den WiFiClient konfigurieren, dass beide bei einer Anfrage auch benutzt werden k\u00f6nnen. Sowohl Zertifikat als auch Key werden wir als char Array \u00e4hnlich dem \u00f6ffentlich Key. Da jedoch der private Key eigentlich nicht mit in das Repository sollte w\u00e4re es angenehm diesen mit in die Datei <code>secret.h<\/code> zu legen. Jedoch habe ich nicht herausgefunden, wie man Pr\u00e4prozessor Statements mit einem char Array komibiniert der mehrere Newlines hat, daher f\u00fcgen wir lieber noch eine Datei <code>secret.cpp<\/code> hinzu, in welcher die Secrets initialisiert werden, w\u00e4hrend sie in der <code>secret.h<\/code> als externe Variablen deklariert werden. Dies hat den Vorteil, dass wir sie in der main.cpp nutzen k\u00f6nnen, aber nicht in ihr deklarieren m\u00fcssen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ cd ${OTA_PROJECT_SIGNING_SECURE_TWO_WAY]\n$ cat src\/secret.h\n#ifndef SECRET_H\n#define SECRET_H\n#define APSSID \"Your APPSID\"\n#define APPSK \"YOUR PASSWORD\"\nextern const char cert&#091;]; \nextern const char private_key&#091;];\n#endif\n$ touch src\/secret.cpp\n$ cat src\/secret.cpp\n#include \"secret.h\"\n#include &lt;Arduino.h&gt;\n\nconst char cert&#091;] PROGMEM = R\"EOF(\n-----BEGIN CERTIFICATE-----\nMIIDYDCCAkgCFAby5WtzERmyqC5tDuNvHyzZnwV1MA0GCSqGSIb3DQEBCwUAMHwx\nCzAJBgNVBAYTAkRFMQswCQYDVQQIDAJCVzELMAkGA1UEBwwCS0ExDDAKBgNVBAoM\nA2lvdDEMMAoGA1UECwwDaW90MRkwFwYDVQQDDBBteWRvbWFpbm5hbWUueHl6MRww\nGgYJKoZIhvcNAQkBFg15b3VyQG1haWwuY29tMB4XDTIxMDYwNzE2NDYyNVoXDTIy\nMDYwMjE2NDYyNVowXTELMAkGA1UEBhMCREUxCzAJBgNVBAgMAkJXMQswCQYDVQQH\nDAJLQTEMMAoGA1UECgwDaW90MQwwCgYDVQQLDANpb3QxGDAWBgNVBAMMDzE5Mi4x\nNjguMTc4LjEyMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMV2CyW0\nlxuv+CZFbKUp2Qzs0rjafUminGbeFw54+IRHhJJSm4ddLmObm29dUYErtUOLDK+i\nj7reWbA4xKyyP\/3mYyRqVQnmLzqe7rCPe1ah3R\/JhF1O95P9jKDlZfxAYw6jhAEG\n0EYCEhoOMfnNRjcXE\/rgJMAYzyXxqudISFKBGA8ci0nyFOSlQd5WL9zUBGt1L967\nzLhTdOOGsVuMZ3sV2fvYRflMFjwMVoO+B81h66THJXo28FQ3eQju5dL3c+uxu2Cm\nl33\/wAJI6Sus8iM1N2270FDhiRkkdiNiQnxGMVuTp+I7hhF8lAtPGKxZwR4DU\/zR\nGw8AYejc+XV4OmMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAC1TT5P6sLg2CmGlZ\n8IG3AIBg\/8Tnhon7nONiF\/0pi\/8uod093WHKGdHUW4KD+dyMk5vo32qwx7cT2oSz\nLvtDibhF01Nivk633g2WHk31KKcvRZ9t72pxIdU\/ZibnYNR4034m\/3F050t6peI9\nMfhIF3+QVkhjkyoLdX2gz1caeVo2u9aR0t7gnMbJByNYbvjXJu0qE94QfHnSSxMd\nq6jVoZBd5eqYJKqZ2I+230CNbqJKsy0SEa0XnypkHK0MFOePjow70izN\/345UreI\n47CM224yaMJDMmjsbJRaPoOTeFSnkTl2DC2PDII60QSLk+1zRWwLhSpgqKz5ERw1\nVhxzNA==\n-----END CERTIFICATE-----\n)EOF\";\n\nconst char private_key&#091;] PROGMEM = R\"EOF(\n-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAxXYLJbSXG6\/4JkVspSnZDOzSuNp9SaKcZt4XDnj4hEeEklKb\nh10uY5ubb11RgSu1Q4sMr6KPut5ZsDjErLI\/\/eZjJGpVCeYvOp7usI97VqHdH8mE\nXU73k\/2MoOVl\/EBjDqOEAQbQRgISGg4x+c1GNxcT+uAkwBjPJfGq50hIUoEYDxyL\nSfIU5KVB3lYv3NQEa3Uv3rvMuFN044axW4xnexXZ+9hF+UwWPAxWg74HzWHrpMcl\nejbwVDd5CO7l0vdz67G7YKaXff\/AAkjpK6zyIzU3bbvQUOGJGSR2I2JCfEYxW5On\n4juGEXyUC08YrFnBHgNT\/NEbDwBh6Nz5dXg6YwIDAQABAoIBAQCc3go9ChqBhGo+\n\/SgyjexAuGMvN2QQ+34EuqrWdIP5kldfZwDnqL8Roqz468m0NGTsI6sQXLSSX9Ig\njPixuWnc6woYA+FK2\/LpPxmgalwxlqV0e0MMXY+Rofv2VkeO\/hezqXNU3XTYKQz4\nZg6NxOXjHhJhW2\/Wq97k5fg\/hMzvvPlT\/sCvPLa2yDLzK5a14BO9TJRIoFwnFObH\nI58OaoINqiP1ZUag0wHys8QiWQ9DV1aQfQZ4nD1U55nxj3ebZ4JEJwFPvbQfpmrc\nOULfLgWXvBITBgHVucwOeusqFC0t6sNZeconyVAP4AKyqLRyphlO3TaPJIL4yqFu\nAcyZgWEBAoGBAOhR3JC45dweWtfOnG10pvJPO7E\/XDStcnXPQ1WpiKAxvVdkUsy7\n0l8z8tnFixu3bexn11m8HnZwPTTf\/eGVltHVOwhsRa2m8VLcd8uRGn0BlW0j9caT\nwBh8H3SAZKvN8Nim9RYZFg9y1xV\/WrGk7ZXQ02p\/TGAeSSuvCHREoaZFAoGBANmW\nlQAphxQsuj0VZbBOGiRgCePZLL9\/3Azx6StNRF2\/IxVlmQD\/q5RjQLYQqVD2IxK1\n1qpHcb126dRSRuG6UEffMKdbSXM3EENH8L8TpQAyIx\/3hWySgvwjM0wJRJgWOHeu\nfz1w8AGWsz\/U6Bg42wnXYevKK205qCKYEFZ7ghyHAoGASSfF+biPgTH5zy0bawgp\nrfGvTVAzW88mVNywSmA5SqB\/C+7md9vJEfuMxHCFLnQjZCcK1BH90bPkQisqigeN\n14N6tFtL0bPZGAuemXaWzbha8mQ5d499FPi4+vmXOdZ+uepREOVTYgf6nKVezMOv\noNaCTG3LghTnW58hXWNjN7UCgYAm6HVWZRahdeoLmDLp1t132bCLDL+isrNfoTZn\nptZtyQr1\/YfhlNZSn3jn1YzhTFIzO07afhIJpiTj8Z55KL7IS1HA62Lz9kmzLj8P\ne+zKXyzGv5UdOAmyGn1GwHWCmJ6aUBqymupf7lm5NVIXWrtYRCpfZnRjgKbfIL\/z\nJvy6KwKBgQCB7qtuxIxf6hjINBnJnZ8J4HBr4eteuCUZcnyMpD+cRY1UlkEiiWM0\nMa5jbaqddUFkABTraAf8SMxZ3TLFhte29wp8yCVibtv\/5rbN8VPCevRy\/5BOX5gD\n\/X36xOFF78qAL42jtYpK90KOKAyfoJ5H7HIRFcoMwTeWyluSaDjIlw==\n-----END RSA PRIVATE KEY-----\n)EOF\";<\/code><\/pre>\n\n\n\n<p>Es wird die lokale Header Datei <code>secret.h<\/code> genutzt, da hier die Variablen initialisiert wurden. Zudem brauchen wir <code>&lt;Arduino.h&gt;<\/code> f\u00fcr das <code>PROGMEM<\/code> Keyword.<\/p>\n\n\n\n<p>Mit char Array des Keys und Zertifikates k\u00f6nnen wir nun diese in der Logik des ESPs zum WiFiClient \u201chinzuf\u00fcgen\u201d, um dann valide Anfragen an unseren Update Server stellen zu k\u00f6nnen. Als Basis f\u00fcr unseren Code ist das Beispiel zu den verschl\u00fcsselten und signierten OTA Updates.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>...\n#if MANUAL_SIGNING\nBearSSL::PublicKey *signPubKey = nullptr;\nBearSSL::HashSHA256 *hash;\nBearSSL::SigningVerifier *sign;\n#endif\n\n+ BearSSL::X509List *list = nullptr;\n+ BearSSL::PrivateKey *key = nullptr;\n+\n+ BearSSL::WiFiClientSecure client;\n\n#include &lt;time.h&gt;\n\n#include &lt;FS.h&gt;\n#include &lt;LittleFS.h&gt;\n...\n  struct tm timeinfo;\n  gmtime_r(&amp;now, &amp;timeinfo);\n  Serial.print(F(\"Current time: \"));\n  Serial.print(asctime(&amp;timeinfo));\n}\n\n+ void client_tls_setup() {\n+   list = new BearSSL::X509List(cert);\n+   key = new BearSSL::PrivateKey(private_key);\n+   client.setClientRSACert(list, key);\n+   delay(1500);\n+ }\n\nvoid setup() {\n...\n  int numCerts = certStore.initCertStore(LittleFS, PSTR(\"\/certs.idx\"), PSTR(\"\/certs.ar\"));\n  Serial.print(F(\"Number of CA certs read: \"));\n  Serial.println(numCerts);\n  if (numCerts == 0) {\n    Serial.println(F(\"No certs found. Did you run certs-from-mozill.py and upload the LittleFS directory before running?\"));\n    return; \/\/ Can't connect to anything w\/o certs!\n  }\n  + client_tls_setup();\n}\n...\nvoid loop() {\n  \/\/ wait for WiFi connection\n  if ((WiFiMulti.run() == WL_CONNECTED)) {\n\n    setClock();\n\n    - BearSSL::WiFiClientSecure client;\n...<\/code><\/pre>\n\n\n\n<p>Mit diesen \u00c4nderungen k\u00f6nnen wir nun den ESP flashen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ pio run --target upload; pio device monitor -b 115200\n\nProcessing nodemcuv2 (platform: espressif8266; board: nodemcuv2; framework: arduino)\n...\nCompressed 455120 bytes to 332607...\nWriting at 0x00000000... (4 %)\n...\nWriting at 0x00050000... (100 %)\nWrote 455120 bytes (332607 compressed) at 0x00000000 in 29.3 seconds (effective 124.5 kbit\/s)...\nHash of data verified.\n--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---\n&#091;SETUP] WAIT 3...\n&#091;SETUP] WAIT 2...\n&#091;SETUP] WAIT 1...\nExists1\nNumber of CA certs read: 1\nWaiting for NTP time sync:\nCurrent time: Thu Jun 10 22:41:21 2021\nMFLN supported: no\nLocal WiFi192.168.178.121\nCALLBACK:  HTTP update process started\n...\nCALLBACK:  HTTP update process finished\n\n ets Jan  8 2013,rst cause:2, boot mode:(3,6)\n\nload 0x4010f000, len 3584, room 16\ntail 0\nchksum 0xb0\ncsum 0xb0\nv2843a5ac\n@cp:0\nld<\/code><\/pre>\n\n\n\n<p>Tats\u00e4chlich ist es uns nun m\u00f6glich das Update Artefakt \u00fcber den ESP anzufragen und dieses als neue Logik auszuf\u00fchren. Somit haben wir nun verschl\u00fcsselte und signierte Updates, welche nur dann m\u00f6glich sind, wenn Client als auch Update Server valide Zertifikate besitzen. Allen Code gibt es so wie immer <a href=\"https:\/\/github.com\/buermarc\/iot-blog-posts\/tree\/main\/two-way-secure-signing-ota\">hier<\/a> auf Github zu finden. Im n\u00e4chsten Blogbeitrag erweitern, wie das Update um das eigentliche Element welches als Trigger dient. Hierf\u00fcr werden wir uns anschauen, wie wir auch eine Verbindung mit einem MQTT Broker aufbauen k\u00f6nnen, wobei wir auch hier Verschl\u00fcsselung der ausgetauschten Nachrichten, als auch Validation von Broker und Client anstreben.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Stand jetzt ist die Kommunikation zwischen ESP und Update Server verschl\u00fcsselt und das Auslesen von Paketen, um unser Update Artefakt zu erhalten ist nicht mehr m\u00f6glich. Gleichzeitig k\u00f6nnen wir dank der Signatur des Artefaktes sicher sein, dass wir nur unseren<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[6],"tags":[],"_links":{"self":[{"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/posts\/621"}],"collection":[{"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/comments?post=621"}],"version-history":[{"count":1,"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/posts\/621\/revisions"}],"predecessor-version":[{"id":622,"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/posts\/621\/revisions\/622"}],"wp:attachment":[{"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/media?parent=621"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/categories?post=621"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/tags?post=621"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}