Dopo aver pubblicato l’articolo su come realizzare un webserver con il chip esp32, alcuni utenti mi hanno scritto segnalandomi giustamente che chiunque, una volta collegato alla rete wifi, potrebbe comandare il relay e chiedendomi come poter controllare l’accesso al sito.
Una soluzione classica (utilizzata anche in questo mio tutorial) è quella di richiedere l’inserimento di una password. Tale soluzione è molto semplice da implementare ma ha come aspetto negativo la necessità di dover digitare la password ogni volta che si desidera comandare il relay.
Oggi voglio mostrarvi come proteggere il vostro sito web tramite una funzionalità del protocollo SSL/TLS, la mutua autenticazione.
Mutua autenticazione
Ogni volta che ci colleghiamo ad un sito con il protocollo https, il server che ospita il sito ci invia il certificato SSL di tale sito. Grazie a questo certificato possiamo verificare la identità del server e stabilire con esso una connessione protetta:
Il protocollo TLS include anche la possibilità che il server richieda al client un certificato di autenticazione: in tal modo è possibile una two-way authentication, ovvero il server e il client si autenticano l’un l’altro tramite appunto certificati SSL:
Utilizzando questa modalità di autenticazione, l’utente dovrà soltanto installare sul proprio dispositivo il certificato client (e la relativa chiave privata) ed eventualmente scegliere quale certificato inviare al server se sul proprio dispositivo ne sono installati più di uno:
L’autenticazione tramite certificati SSL si basa quindi sul principio di qualcosa che possiedo (come ad esempio una chiave) e non di qualcosa che conosco (come ad esempio una password).
Certificati
In generale i certificati SSL possono essere di due tipi:
[checklist]
- self-signed (auto-firmati)
- emessi da una Certification Authority
[/checklist]
I primi vanno bene per usi interni o di test, mentre i secondi, emessi normalmente da CA fidate, sono largamente usati in ambiente di produzione o su Internet. Vi sarà probabilmente capitato questo messaggio di avviso:
che indica proprio che il vostro browser non riconosce come valido il certificato inviato dal server proprio perché firmato da una CA non fidata.
Per l’esempio di oggi, utilizzeremo OpenSSL come certification authority per creare i certificati che ci servono. Se state utilizzando la toolchain fornita da Espressif, non è necessario installare nulla visto che OpenSSL è già incluso.
Iniziamo creando una cartella che conterrà tutti i files necessari per la CA all’interno della nostra home directory:
cd mkdir myCA |
Spostiamoci all’interno di tale cartella e creiamo alcuni files e cartelle vuoti:
cd myCA mkdir csr certs crl newcerts private touch index.txt echo 1000 > serial |
Copiamo il file di configurazione della certification authority (openssl.cnf) dal mio repository Github alla cartella myCA. Apriamo il file e modifichiamo il path principale:
Dobbiamo ora generare la chiave privata e il certificato della nostra CA. Iniziamo con la chiave (sarà lunga 2048 bit, dimensione sufficiente per garantire una buona sicurezza):
winpty openssl genrsa -aes256 -out private/ca.key 2048 |
(i comandi devono esseere preceduti da winpty solo sotto Windows!)
Dobbiamo scegliere una password; è importante ricordarla perché ci verrà richiesta ogni volta che vorremo utilizzare la CA:
Generiamo ora il certificato della CA (durata 3650 giorni, ovvero 10 anni):
winpty openssl req -config openssl.cnf -key private/ca.key -new -x509 -days 3650 -sha256 -extensions ca_cert -out certs/ca.cer |
Dobbiamo indicare alcune informazioni… l’unica obbligatoria è il common name che identificherà il nome della CA sui vari certificati. Al termine del processo possiamo aprire il certificato generato (file ca.cer) e verificare il nome e la scadenza:
Passiamo ora alla generazione dei certificati server e client. Il procedimento è il medesimo:
[checklist]
- genero una nuova chiave privata – openssl genrsa
- genero il file CSR (Certificate Signing Request – richiesta di certificato) – openssl req
- firmo il file CSR con la CA in modo da ottenere il certificato – openssl ca
[/checklist]
Certificato server:
winpty openssl genrsa -out private/esp-server.key 2048 winpty openssl req -config openssl.cnf -key private/espserver.key -new -sha256 -out csr/espserver.csr winpty openssl ca -config openssl.cnf -extensions server_cert -days 365 -notext -md sha256 -in csr/espserver.csr -out certs/espserver.cer |
Certificato client:
winpty openssl genrsa -out private/esp-user.key 2048 winpty openssl req -config openssl.cnf -key private/esp-user.key -new -sha256 -out csr/esp-user.csr winpty openssl ca -config openssl.cnf -extensions usr_cert -days 365 -notext -md sha256 -in csr/esp-user.csr -out certs/esp-user.cer |
Come spiegato sopra, il dispositivo client deve possedere sia il certificato client che la relativa chiave privata. Possiamo unire certificato e chiave in un unico file p12 (o pfx) con il comando:
winpty openssl pkcs12 -export -out esp-user.pfx -inkey private/esp-user.key -in certs/esp-user.cer |
La procedura di installazione del file pfx dipende dalla piattaforma: su Windows è sufficiente un doppio-click sul file e seguire il wizard di importazione.
SSL webserver
Il codice sorgente del programma in esecuzione sul chip esp32 è disponibile nel mio repository Github.
Per prima cosa dobbiamo copiare nella cartella di progetto i certificati (CA e server) e la chiave privata (server) e – includerli come text data nel programma (vi ho già mostrato come fare per le immagini del precedente esempio):
Per la gestione del protocollo SSL utilizziamo la libreria mbedTLS, già inclusa nel framework.
Includiamo tutti gli headers necessari:
#include "mbedtls/platform.h" #include "mbedtls/net.h" #include "mbedtls/ssl.h" #include "mbedtls/entropy.h" #include "mbedtls/ctr_drbg.h" #include "mbedtls/debug.h" #include "mbedtls/error.h" |
Prima di poter accettare connessioni SSL, è necessario definire e inizializzare le diverse variabili che andremo a utilizzare:
// mbed TLS variables mbedtls_ssl_config conf; mbedtls_ssl_context ssl; mbedtls_net_context listen_fd, client_fd; mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; mbedtls_x509_crt srvcert; mbedtls_x509_crt cachain; mbedtls_pk_context pkey; [...] // initialize mbedTLS components mbedtls_net_init(&listen_fd); mbedtls_net_init(&client_fd); mbedtls_ssl_config_init(&conf); mbedtls_ctr_drbg_init(&ctr_drbg); mbedtls_entropy_init(&entropy); mbedtls_x509_crt_init(&srvcert); mbedtls_x509_crt_init(&cachain); mbedtls_pk_init(&pkey); |
Carichiamo i certificati e la chiave privata nelle variabili della libreria:
mbedtls_x509_crt_parse(&cachain, ca_cer_start, ca_cer_end - ca_cer_start); mbedtls_x509_crt_parse(&srvcert, espserver_cer_start, espserver_cer_end - espserver_cer_start); mbedtls_pk_parse_key(&pkey, espserver_key_start, espserver_key_end - espserver_key_start, NULL, 0); |
Indichiamo ora alla libreria quali variabili utilizzare e richiediamo la mutua autenticazione (MBEDTLS_SSL_VERIFY_REQUIRED):
mbedtls_ssl_conf_ca_chain(&conf, &cachain, NULL); mbedtls_ssl_conf_own_cert(&conf, &srvcert, &pkey); mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED); |
Dopo aver configurato gli altri aspetti della libreria (random number generator e funzione di debug) possiamo associarla alla porta standard per il protocollo https (443) e accettare le connessioni in ingresso:
mbedtls_net_bind(&listen_fd, NULL, "443", MBEDTLS_NET_PROTO_TCP); mbedtls_net_accept(&listen_fd, &client_fd, NULL, 0, NULL); |
Una volta accettata la connessione, è sufficiente chiamare la funzione:
mbedtls_ssl_handshake(&ssl); |
perché la libreria invii al client il proprio certificato SSL server e richieda il certificato client, effettuandone poi la verifica: la mutua autenticazione è così garantita!
Il resto del programma ricalca l’esempio precedente, utilizzando le funzioni di invio/ricezione della libreria mbedTLS (mbedtls_ssl_read e mbedtls_ssl_write).
Test
(sottotitoli in italiano disponibili)
Ciao Luca
ho un problema di riavvio continuo su questo esempio. Il trap avviene in:
HTTPS Server starting…
– Wifi interface configured
– DHCP server started
I (182) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (182) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
ESP_ERROR_CHECK failed: esp_err_t 0xb at 0x40107b0f
0x40107b0f: ap_setup at /home/orcim/esp/_work/_luca_dentella/esp32-tutorial/15_ssl_webserver/firmware/main/./main.c:355 (discriminator 1)
file: “/home/orcim/esp/_work/_luca_dentella/esp32-tutorial/15_ssl_webserver/firmware/main/./main.c” line 355
func: ap_setup
expression: esp_wifi_set_config(WIFI_IF_AP, &ap_config)
Ciao Loris, normalmente quando va in errore il metodo
esp_wifi_set_config
significa che qualche parametro è errato. Controlla ad esempio la password, spesso non è sufficientemente lunga per l’algoritmo (WPA…) scelto.Ciao Luca,
complimenti per i tutorials.
Volevo solo segnalare che in questo esempio, il nome da utilizzare per creare il certificato del server deve essere “espserver” e non “esp-server”
altrimenti il sistema genera errore quando tenta d’importare il file.
grazie sistemo il refuso!
Ciao Luca,
se mi connetto direttamente all’AP dell’ESP32 funziona perfettamente ma se tento di accedere attraverso un router wifi, a cui sia il PC che l’ESP32 sono connessi, non funziona.
Con il server web http invece non ci sono problemi.
Sbaglio qualcosa?
Grazie mille.
ciao, molto strano… i test che ho effettuato erano proprio con l’esp32 in STAtion mode.
Ciao, ho risolto: era un mio problema di quantità di RAM allocata dell’ESP32.
Grazie mille.
Ciao!
ciao Luca,
alla firma dei CSR sia server che client ho questo messaggio:
Using configuration from openssl.cnf
Error opening CA private key /home/claud/myCA/private/ca.key
3228:error:02001003:system library:fopen:No such process:bss_file.c:406:fopen(‘/
home/claud/myCA/private/ca.key’,’rb’)
3228:error:20074002:BIO routines:FILE_CTRL:system lib:bss_file.c:408:
unable to load CA private key
ciao, sembra ci sia qualche errore nel setup della tua CA, guarda questa guida di RH
OK, rifatto da zero.la firma del certificato server va bene:
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
Ma quando firmo il Client mi da:
Sign the certificate? [y/n]:y
failed to update database
TXT_DB error number 2
quell’errore normalmente indica che stai generando due certificati con gli stessi parametri
Grazie risolto, dai disegni non era visibile e sto iniziando ora con questo ambiente di sviluppo.
Due curiosità: all’inizio usi la shell mingw32, ma dopo installato esp-idf sei nella shell MSYS.
aprendo la shell MSYS, per i certificati tutto ok, ma se faccio make, mi dice: esp-idf build system only supports MSYS2 in “MINGW32” mode.
Altra curiosità: in un successivo articolo spieghi la creazione di un certificato, ma la sintassi è leggermente diversa. E’ migliore di questa?
ancora Grazie
Hi
I’m getting the same error message as Claudio did originally but my Italian is not good enough to properly understand the solution.
Also, I notice that the extra hyphen is still shown in the “winpty openssl genrsa -out private/esp-server.key 2048” line that Steve mentioned.
Ciao Luca,
sfrutto questo post per chiedere qualcosa di simile. Ho la necessità di leggere tramite javascript il contenuto di un file per un webserver. Il file in questione dovrebbe essere aggiornato con la lettura di un sensore in modo tale da poter visualizzare ogni volta il valore letto senza la necessità di ricaricare la pagina web del webserver. Con lo spiffs è possibile far ciò? grazie
ciao Rossella! La cosa è ancora più facile: tramite Javascript puoi fare una chiamata al tuo webserver (tipo “dammi il valore del sensore”) e aggiornare solo un elemento della pagina senza doverla ricaricare tutta. Se quindi scheduli la tua chiamata ogni tot secondi, ecco che hai risolto. E’ così che funzionano i siti “AJAX”, guarda ad esempio il mio progetto ESP32Lights, faccio così per aggiornare il valore del sensore di luminosità ogni 5 secondi.
Si, ho già visto il tuo esempio, ma nel mio caso sto utilizzando un webserver https, tramite openssl. Cambia qualcosa?
Grazie
ciao, no non cambia nulla… jQuery può tranquillamente interrogare anche un webservice in https.
Ciao Luca,
complimenti per i tuoi articoli.
Sto provando a connettere un Sonoff con una ESP8266 ad un broker mqtt che richiede la mutua autenticazione. Ho sia il certificato client che quello server.
Posso comunque utilizzare la libreria mbedTLS per potermi collegare?
Grazie
ciao Silvio, purtroppo non saprei dirti per l’esp8266, questi tutorial sono per l’esp32 che ha un diverso framework…