Generează un certificat SSL propriu și folosește-l pentru securizarea unui server web pe ESP8266 NodeMcu
NodeMcu este o placă de dezvoltare bazată pe platforma ESP8266. Acest microcontroller este destinat utilizării în aplicații IoT, fiind dotat cu conectivitate WiFi. În articolele anterioare am arătat că plăcile de dezvoltare cu acest cip pot fi programate în mediul de dezvoltare Arduino și am creat un server web simplu.
În ziua de astăzi, securitatea este foarte importantă. Poate vei folosi ESP8266 doar în rețeaua locală sau poate vei permite accesul la serverul ce rulează pe acesta din exterior. În ambele situații, utilizarea de conexiuni securizate este importantă. În ultimii ani majoritatea site-urilor web au trecut de la protocolul standard HTTP la cel securizat, HTTPS. Pentru a putea oferi conținut securizat, serverul trebuie să prezinte clientului un certificat semnat de o autoritate emitentă de încredere. Certificatele au valabilitate limitată.
Vei afla în acest articol cum să generezi un certificat de securitate și să programezi un server securizat pe ESP8266. Certificatul poate fi obținut (contra cost, de multe ori) de la un emitent autorizat sau poate fi generat pe propriul computer. Voi alege a doua metodă, deși vine cu un aspect negativ. Navigatorul web îți va afișa un mesaj de eroare dacă certificatul nu este emis de o autoritate cunoscută. Mesajul trebuie luat în seamă în majoritatea cazurilor. Dar aici, certificatul va fi generat chiar de tine.
HTTPS folosește pe lângă acel certificat public, accesibil clientului, și o cheie secretă cunoscută doar de server. Clientul criptează datele trimise către server cu cheia publică extrasă din certificat, iar serverul decriptează aceste date cu cheia secretă. Pentru a decripta traficul, cineva ar trebui să folosească atât cheia publică din certificat, cât și pe cea secretă, de la server. Emitenții autorizați furnizează cheia secretă împreună cu certificatul. Cât timp cheia secretă pe care o vei genera este cunoscută doar de ESP8266, traficul între un client și serverul tău este la fel de sigur ca și în cazul unui certificat emis de o autoritate cunoscută. Browser-ul va afișa mesajul tipic de eroare, dar atât timp cât doar tu ai cheia secretă stocată pe server, conexiunea este sigură.
Mai trebuie să știi că ESP8266 nu este un microcontroller optimizat pentru criptarea și decriptarea traficului SSL. Se recomandă ca pentru toate programele care utilizează SSL, frecvența procesorului să fie de 160 MHz. Chiar și așa, unele proceduri pot cauza întârzieri de ordinul secundelor. Rareori, o solicitare SSL către ESP8266 poate genera resetarea microcontroller-ului, dacă necesită un timp de procesare mai mare de 5 secunde. Recomand folosirea celui mai nou kit de dezvoltare (actualizarea din managerul de plăci din Arduino IDE).
Certificatul și cheia RSA
Poți obține certificatul și cheia de la orice autoritate de încredere, dacă vrei. Pentru a funcționa pe ESP8266, certificatul trebuie să folosească algoritmul criptografic SHA256 și să fie de 1024 sau 512 biți. Este de preferat cheia de 512 biți pentru viteză, respectiv cheia de 1024 biți pentru securitate și acceptare de către browser-ele moderne. Emitentul trebuie să îți ofere certificatul propriu-zis și cheia secretă.
Însă, pentru acest articol, voi genera cele două chei folosind OpenSSL. Nu este o problemă să găsești și să instalezi OpenSSL pe Linux, dar un fișier de instalare pentru Windows este ceva mai greu de găsit. Pe site-ul slproweb.com vei putea descărca din secțiunea Download Win32 OpenSSL fișiere de instalare. Descarcă formatul exe, versiunea Light corespunzătoare arhitecturii sistemului de operare pe care îl folosești și rulează fișierul pentru a-l instala.
Lansează openssl
la linia de comandă, din folderul în care vrei să fie generate cheia și certificatul. Iată cum se face acest lucru pe Linux (în Terminal), respectiv pe Windows (în PowerShell):
cd ~/Desktop openssl
cd Desktop &"C:/Program Files/OpenSSL-Win64/bin/openssl.exe"
Poți genera atât certificatul cât și cheia într-o singură comandă, astfel:
req -x509 -newkey rsa:1024 -sha256 -keyout key.txt -out cert.txt -days 365 -nodes -subj "/C=RO/ST=B/L=Bucharest/O=OneTransistor [RO]/OU=OneTransistor/CN=esp8266.local" -addext subjectAltName=DNS:esp8266.local
sau cu mai multe comenzi:
genrsa -out key.txt 1024 rsa -in key.txt -out key.txt req -sha256 -new -nodes -key key.txt -out cert.csr -subj '/C=RO/ST=B/L=Bucharest/O=OneTransistor [RO]/OU=OneTransistor/CN=esp8266.local' -addext subjectAltName=DNS:esp8266.local x509 -req -sha256 -days 365 -in cert.csr -signkey key.txt -out cert.txt
În prima comandă, rsa:1024
, iar în a doua ultimul argument al genrsa
specifică mărimea cheii în biți. Parametrul -days
reprezintă durata de valabilitate a certificatului, începând de la momentul în care a fost generat.
Găsești cheia în fișierul key.txt și certificatul în cert.txt. Înainte de a le folosi, la generarea lor a fost folosit argumentul -subj
. Acesta este „subiectul” certificatului și are următorii parametri pe care îi setezi cum vrei:
- C - țara, nume scurt (country)
- ST - unitate teritorială, provincie (state)
- L - localitate
- O - organizație
- OU - unitate organizațională
- CN - nume comun (numele domeniului)
De asemenea atributul subjectAltName
trebuie să conțină domeniul la care poate fi accesat serverul, cât și adrese IP, dacă acestea sunt statice. De exemplu, pentru IP, ar trebui specificat astfel: subjectAltName=DNS:esp8266.local,IP:192.168.1.10
. Mesajul de eroare pe care îl vei primi la accesare va fi legat de autoritatea emitentă a certificatului. Acest lucru înseamnă că browser-ul nu cunoaște organizația care a emis certificatul.
Eroarea legată de emitentul necunoscut al certificatului (în Google Chrome)
Serverul web
Vom porni de la schița din articolul anterior, pe care o vom modifica pentru HTTPS. Când tastezi doar esp8266.local în bara de adresă a broswer-ului, acesta va încerca conectarea prin HTTP. Dacă pe ESP8266 rulează doar un server HTTPS, conexiunea prin HTTP va fi refuzată. Acest lucru nu este user-friendly, de aceea vom lăsa serverul HTTP în funcțiune, doar pentru a trimite un header 301 către client. Adică, la primirea unei solicitări HTTP, vom informa browser-ul că trebuie să acceseze o adresă HTTPS. Redirecționarea are loc instant.
În schiță, trebuie să punem cheia și certificatul.
#include <ESP8266WiFi.h> #include <ESP8266mDNS.h> #include <ESP8266WebServer.h> #include <ESP8266WebServerSecure.h> const char *ssid = "retea"; const char *password = "parola"; const char *dname = "esp8266"; BearSSL::ESP8266WebServerSecure server(443); ESP8266WebServer serverHTTP(80); static const char serverCert[] PROGMEM = R"EOF( pune aici continutul fisierului "cert.txt" )EOF"; static const char serverKey[] PROGMEM = R"EOF( pune aici continutul fisierului "key.txt" )EOF";
În setup()
, înainte de configurarea celor două servere, obținem data și ora de la un server de timp cu ajutorul funcției configTime()
. Primul parametru al acestei funcții este decalajul orar față de GMT, în secunde. Apoi, pornim serverul HTTP, atribuindu-i funcția de redirecționare. În cele din urmă, configurăm serverul HTTPS cu cheia și certificatul și îl pornim.
void setup() { pinMode(D0, OUTPUT); Serial.begin(115200); if (!connectToWifi()) { delay(60000); ESP.restart(); } configTime(3 * 3600, 0, "ro.pool.ntp.org", "time.nist.gov"); serverHTTP.on("/", redirectionareHTTPS); serverHTTP.begin(); server.getServer().setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey)); server.on("/", afisareSite); server.begin(); Serial.println("Serverul a fost pornit"); }
În loop()
ne asigurăm că ambele servere gestionează solicitările de la client.
void loop() { serverHTTP.handleClient(); server.handleClient(); MDNS.update(); }
Funcția de redirecționare HTTPS este foarte simplă. Vine însă cu un posibil dezavantaj. Redirecționează către numele dat prin multicast DNS. În cazul în care clientul nu suportă mDNS, conexiunea nu va avea loc decât prin accesarea IP-ului. Nu ar fi fost greu să generez header-ul de redirecționare cu IP-ul local al server-ului. Dar acest lucru duce la altă problemă. Certificatul este configurat pentru domeniul esp8266.local, nu pentru un IP. Nu poți genera certificatul pentru IP decât dacă acesta este static. Dacă atributul CN al certificatului, atributul SAN și domeniul nu corespund, server-ul poate fi accesat, după acceptarea certificatului considerat invalid de browser.
void redirectionareHTTPS() { serverHTTP.sendHeader("Location", String("https://esp8266.local"), true); serverHTTP.send(301, "text/plain", ""); }
În această schiță, răspunsul dat de server clientului conține inclusiv data și ora curente. În funcția afisareSite()
am adăugat câteva rânduri:
content += "<p>"; time_t now = time(NULL); content += ctime(&now); content += "</p>";
Aspectul server-ului accesat din Google Chrome
Fără erori în Chrome
Există, totuși o metodă prin care poți scăpa de mesajul de eroare „Nesecurizat”. În primul rând cheia trebuie să fie de 1024 biți. Criptarea pe 512 biți nu este considerată suficient de sigură.
Browser-ul trebuie informat cu privire la existența unui certificat propriu de încredere. Accesează chrome://settings/certificates (doar pe Linux) sau mergi la Setări - Avansate - Gestionează Certificatele. În fila Autorități (Trusted Root Certification Authorities) apasă pe Importă. Alege fișierul cert.txt pe care l-ai generat cu OpenSSL. Dacă nu apare, modifică tipul la Toate fișierele în dialogul de selectare. Pe Linux, acordă-i încredere pentru identificarea site-urilor. Pe Windows, după import, selectează-l în listă, apasă pe Advanced și bifează Client Authentication. Închide browser-ul și deschide din nou pagina server-ului. În Windows certificatul devine disponibil la nivel se sistem, pentru toate browser-ele.
Verificare securitate server (Chrome - F12)
Resurse
Codul sursă complet, împreună cu fișierele aferente certificatului sunt disponibile pe GitHub. Modifică schița pentru rețeaua ta WiFi (nume și parolă) și, foarte important, folosește propriul certificat. Cel folosit de mine nu mai oferă niciun fel de protecție deoarece a fost făcut public.
Niciun comentariu :
Trimiteți un comentariu
Vă recomandăm să citiți regulamentul comentariilor înainte de a scrie un comentariu.