Nel mondo odierno sempre più connesso, garantire la sicurezza e la privacy dei sistemi embedded e dei dispositivi IoT è più critico che mai. Questo articolo approfondisce il regno degli aggiornamenti firmware sicuri esplorando come implementare gli aggiornamenti self-OTA (Over-The-Air) per i dispositivi ESP32 utilizzando HTTPS (SSL/TLS) con un certificato autofirmato attendibile. Come tecnica essenziale per mantenere la sicurezza e l’affidabilità dei tuoi progetti, gli aggiornamenti self-OTA consentono la distribuzione remota degli aggiornamenti del firmware, garantendo al tempo stesso una comunicazione sicura tra i dispositivi e i server di aggiornamento.
Inizieremo esaminando la necessità di aggiornamenti OTA sicuri, seguiti da una guida dettagliata sulla generazione di un certificato SSL/TLS autofirmato, sulla configurazione di ESP32 con le librerie necessarie e sulla configurazione del server di aggiornamento. Alla fine di questo articolo, avrai una comprensione completa di come incorporare gli aggiornamenti OTA automatici basati su HTTPS nei tuoi progetti ESP32, migliorando la sicurezza e l’efficienza dei tuoi dispositivi IoT.
Gli aggiornamenti OTA possono essere effettuati utilizzando:
- Arduino IDE
- Web Browser
- Server HTTP
Prima di tutto, controlla il tutorial “ESP32: flash del firmware binario compilato (.bin)“.
Qui alcuni dispositivi comuni ESP32 Dev Kit v1 - TTGO T-Display 1.14 ESP32 - NodeMCU V3 V2 ESP8266 Lolin32 - NodeMCU ESP-32S - WeMos Lolin32 - WeMos Lolin32 mini - ESP32-CAM programmer - ESP32-CAM bundle - ESP32-WROOM-32 - ESP32-S
Aggiornamento endpoint con controllo della versione e connessione sicura TLS
Ora possiamo aggiornare il nostro esempio con una connessione SSL/TLS.
Per generare un buon certificato Self-Signed, è necessario associare un nome comune o un IP della macchina che si desidera chiamare.
Tuttavia, ESP32 e esp8266 non supportano l’IP (ho perso un giorno a provarlo), quindi l’unico modo per testare una soluzione reale è assegnare un nome host alla macchina server.
Se si desidera effettuare una soluzione veloce, è possibile utilizzare il comando
client.setInsecure();
che impedisce tutti i controlli di validazione del server e consente di effettuare richieste HTTPs senza un certificato di fiducia.
Ma procediamo per gradi.
Generazione di un certificato Self-signed
Ma se si desidera utilizzare un certificato Self-signed, è possibile utilizzare OpenSSL per la generazione di una chiave privata e un certificato.
Certificato server di base (è necessario bypassare la validazione del certificato sul client)
È possibile ottenere OpenSSL scaricando la versione Linux da tutti i gestori di pacchetti, o scaricandolo per Windows da qui, per utilizzarlo con maggiore semplicità aggiungilo al percorso bin.
openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout server.key -out server.crt -subj "/CN=192.168.1.70"
ed ecco la risposta
Generating a RSA private key
.....+++++
..................................................................................+++++
writing new private key to 'server.key'
-----
Ora hai un server.key
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPwU8Kbzr4PiKO
k+S4Cwj20tgnxYlesv99Fbmivl7PjwhYaIEX68afazYUoeQa1nNM8GSKRh4gSZnR
Zk61RR5d1U/EtWRVFWhIUB2U4Vy6KQ3vUaVoXRjtHjzlvarulvLvHF3/37rTJMqg
rnP2EbdKUQyxuAW7P3OMfSLEzn1LbuPGtZRHepckXnOpddghBvqIRrvrM8GPo0Ro
AXziXZMQ4hQgSxZopnP62XintfM1f/eZVPws9ww/5DVLFQs0ji2LuCPtxaokNgkq
t0wGHS6QNaWDbB7IBhbeg7oLg0HHz2SMJkYrGkmWhku+BVkWWf9snMK/RjN2NNMA
zMGADoSPAgMBAAECggEBAMQ8NHuHquy2TA/edAi/K51wdInElUekzZyJ+8lUBdwJ
n3laZK2CoB8OtotwizQqYchHvL+7EVOwEaFwAGJKQi+hf/Iu3/FaAiFjwz1QTTTt
+GKX/SQB47x9dkoPCDjKzfa7FbLN0fsEYny5q4C/JSEGQ3ZOeuNuQKdvv3qkDEdF
Z90JBHWzOrfUR2SpHuuk366ixwBjohOwyOHSP8VLXfPYp/RcgU/7/cRBSPN+xxIB
2Ovv/ERnTUbLqYlgU0Ok5KMMc7RmtXO9mwE6wFwNGJImYgn4IcCxAe41h6UTxmaG
gdeS2Ixil20Fi7wuLntOGkaqGewdjmjZN2apJ33gCukCgYEA7zE0hVzgfzLXPRVe
y7ouuLLrhZlkJfFsk75aVv9vfLD4eX6JgN/VU34vqmzqVSdszfum6fWyycSXwc9s
xrCLMYMS9lbz/HuiQn1c+7mZT+3hF4Qazy4ZU1rA4CBgGoyJK4eiO7tC9Vl29PvR
osDTdCOjJbIOCIAKsswrixHfgDsCgYEA3lqWDEuxQTctplL6gtXRUyBAooMLrrGZ
9/5Ut3qLlTWqdKr5+RlJCF4hc/1iYQHxDq+6SV2GpOO/0E7w0GlcoO3ME+AMf4qq
OHMcAGoJXYCEw/0u2MjuEnXlo8L3qp1+LK9rvGUViCx7Fha4OCIRuNWeJ4g8eIAl
qqzUd6fJ+70CgYEAiBDEoMzZxGIGgPAEMf5ij5zwT9qXVuJEcrNJLs41/IergWmT
DOXHs6xHrzov/2rjATYTmGm1YAPelFjLtyc8t1ip08pZFxq5jftEhsnoxcg7UKZM
nejKbVll+NlR8ihZ65JHnpUDHRDck7YgZeYtI5cWOt5UD0/PRjDQ4Fa1fnsCgYAL
+gEfBGy1wI0a6llH9tBhk9knZ7uXW0WZz/8sKtOd49gclKEGQ9BlPPy8TKeh5gLf
8aMtFHaOJx5L0BS0hRhSKrzVTTofHI7yn3CgrRV4DdYY4GhHkPsRz3vhCD1i2TzU
l1ZMPX2dahfvJqYhj+Q4enkcVAA91VkyCkEfeNAuWQKBgH+82agy+jlRyRBvQ6eI
i5n7FbCMJSw/1mfOmxGiEQhshaQkPDx+PfPgy+LOMuXAr1ZBsWMCGERdUsesXjIH
AHcY8Nl0tC5dG8q7B3A11xttwFbhQ4fgq+AUEzSH3ykTvuobGCNtM5AuawNWIjj7
VO0JDqUFQcom1brJIHowKKNU
-----END PRIVATE KEY-----
E server.crt
-----BEGIN CERTIFICATE-----
MIIDGTCCAgGgAwIBAgIULbAi2zs41Rh6AZy08xumoYMIFJQwDQYJKoZIhvcNAQEL
BQAwHDEaMBgGA1UEAwwRMTkyLjE2OC4xLjcwOjM0NDMwHhcNMjEwOTEzMDcwODUw
WhcNMjIwOTEzMDcwODUwWjAcMRowGAYDVQQDDBExOTIuMTY4LjEuNzA6MzQ0MzCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM/BTwpvOvg+Io6T5LgLCPbS
2CfFiV6y/30VuaK+Xs+PCFhogRfrxp9rNhSh5BrWc0zwZIpGHiBJmdFmTrVFHl3V
T8S1ZFUVaEhQHZThXLopDe9RpWhdGO0ePOW9qu6W8u8cXf/futMkyqCuc/YRt0pR
DLG4Bbs/c4x9IsTOfUtu48a1lEd6lyRec6l12CEG+ohGu+szwY+jRGgBfOJdkxDi
FCBLFmimc/rZeKe18zV/95lU/Cz3DD/kNUsVCzSOLYu4I+3FqiQ2CSq3TAYdLpA1
pYNsHsgGFt6DuguDQcfPZIwmRisaSZaGS74FWRZZ/2ycwr9GM3Y00wDMwYAOhI8C
AwEAAaNTMFEwHQYDVR0OBBYEFKyvsHqrsHBj+V5H8aDT5qvPOSfdMB8GA1UdIwQY
MBaAFKyvsHqrsHBj+V5H8aDT5qvPOSfdMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
hvcNAQELBQADggEBALwdlitiQayOG2o2y3BAipcdaMxdGbihorheuO17glsZtp2R
H2LUUqnmG8hGH4pHbkd2gozxLt/h7ILrD1xK/EIlXjGueu3MF377GWZpR18OXt+1
cqpxDdAESD3PRbcJnu75uffFYLSKizbuaTbV2D8RY1kcUxRlrFG2bciNP9widbjq
T9B4RhtJb0d+Sk8Z2G2eTlNxESPQ/Oi530rrbZsez0XmdYyogbVHuNdwJvfQdA8U
F1UtWj6vtGAramUn0dS5rJ8REfZ4jdGsXYT2jmCfDTgaE+1sEYm6ry+RInDK5RVi
Kk+bL292uc4d0STCqXciv7YbWDWlZPo9fH0Pxs0=
-----END CERTIFICATE-----
Certificato self-signed valido
Se non è necessario creare un ambiente reale, passare al paragrafo successivo, il certificato generato nel capitolo precedente è sufficiente per il nostro test.
Prima di tutto, è necessario assegnare un nome all’host del server, ci sono molte alternative, ma penso che il modo più veloce sia utilizzare un Dynamic DNS.
Normalemente il Dynamic DNS viene utilizzato per assegnare un nome dinamicamente all’IP fornito dal nostro ISP, ma è possibile assegnare un IP statico e utilizzarlo come un host normale. Ecco un esempio con un servizio gratuito NO-IP.
Configurazione del servizio Dynamic DNS
Vai su https://www.noip.com/ e Registrati.
Inserisci la tua email, una password e crea un nome come serverota.ddns.net
, quindi clicca su Free Sign Up.
Dopo la conferma di iscrizione tramite email, vai a questo link https://my.noip.com/dynamic-dns e puoi vedere l’elenco delle voci Dynamic DNS.
Ora clicca su modifica e inserisci l’indirizzo IP del tuo server NodeJS, per me, adesso è 192.168.1.70.
Applicare e attendere alcuni minuti. Successivamente (su Windows) vai con cmd e avvia ipconfig /flushdns
.
C:\Users\renzo>ipconfig /flushdns
Configurazione IP di Windows
Cache del resolver DNS svuotata.
C:\Users\renzo>ipconfig /flushdns
Windows IP Configuration
Successfully flushed the DNS Resolver Cache
Ora puoi provare a eseguire il ping del dominio.
C:\Users\renzo>ping serverota.ddns.net
Esecuzione di Ping serverota.ddns.net [192.168.1.70] con 32 byte di dati:
Risposta da 192.168.1.70: byte=32 durata<1ms TTL=128
Risposta da 192.168.1.70: byte=32 durata<1ms TTL=128
Risposta da 192.168.1.70: byte=32 durata<1ms TTL=128
Per testare la risoluzione specificata del DNS, puoi utilizzare questo script (Linux).
# Renzo Mischianti <www.mischianti.org>
# Specify DNS server
dnsserver="8.8.8.8"
# function to get IP address
function get_ipaddr {
ip_address=""
# A and AAA record for IPv4 and IPv6, respectively
# $1 stands for first argument
if [ -n "$1" ]; then
hostname="${1}"
if [ -z "query_type" ]; then
query_type="A"
fi
# use host command for DNS lookup operations
host -t ${query_type} ${hostname} &>/dev/null ${dnsserver}
if [ "$?" -eq "0" ]; then
# get ip address
ip_address="$(host -t ${query_type} ${hostname} ${dnsserver}| awk '/has.*address/{print $NF; exit}')"
else
exit 1
fi
else
exit 2
fi
# display ip
echo $ip_address
}
hostname="${1}"
for query in "A-IPv4" "AAAA-IPv6"; do
query_type="$(printf $query | cut -d- -f 1)"
ipversion="$(printf $query | cut -d- -f 2)"
address="$(get_ipaddr ${hostname})"
if [ "$?" -eq "0" ]; then
if [ -n "${address}" ]; then
echo "The ${ipversion} adress of the Hostname ${hostname} is: $address"
fi
else
echo "An error occurred"
fi
done
E puoi eseguirlo in questo modo:
$ ./testdns.sh serverota.ddns.net
The IPv4 adress of the Hostname serverota.ddns.net is: 192.168.1.70
Generare un certificato autofirmato
Puoi utilizzare il comando base già utilizzato modificando il nome comune in questo modo.
openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout server.key -out server.crt -subj "/CN=serverota.ddns.net"
e ora abbiamo server.key
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDFe10Ysfohkqek
lgLLIj09Y3p1B7jAzQnzmWoX6iaPGYcJjhZB843o/AjGWGcMMYLimP/abbAWQimD
WwPIyZI7DxDRztm7eeoOTmCm0pa2SNB2Y6WxrjyMBfj1/C9voXDKH4fP3iUu4JNC
Ln0sbZJQ+65teHvhy3WkqdzynCC0i4KVJJuBwsIaSECctuNMlp63hR6aBVxRyiuW
arAMpt1VBH7kMvpuJXHL5wslzAqof3fGuP2GHUjiS1ZIGkz/EieUacDUVrSq0asz
qb53e6oT8YvoykzSQi9PLHIVWT4uU+uhBLzM3JFTUoDDnURxPmpyPoWRUxWlKh3I
BGgk/CcHAgMBAAECggEAPUNfByk3arDfVvy+kxvlGaVBuQqTMySooKyIMDEQkzYd
IUa1+vG+pXeClV2ZYjv92aQTQ0Th6pXN4RUcIG7/8VhkJGJiQ3m3tYZ1D2unG5eN
LB0PqwZdfCbiLBV29c+EeP+9FyxYJqm+pTpc1KqNSJg7bXSCIfMHZPTZOV5b+wX9
B8IjzaGa6pG34vdu3rTrZHxPWxNzkdlxOBN5gB+oLkpgNZ8xqgKK9jJCRWt7JTM8
mvV5/USDAmvuq6LDF3hHGA54OGoC0DQ0RNEr/7TBOdsPgEtQ+JUM46erCeggkaxi
NTnId/kWJt5PMiPpIw7OHnaEB9dptd5do/DqzQKLwQKBgQDm1r6nT4CDQ1Mlo8Mu
1TOG3L+RmoPOx8Mgal3m3+0IQKQfy1uCHJ9CeMEwTptDBQgStiR8hmvL/nSUmnSy
wjs2Hy9pdz+JfdqHgczGksd62F+nBN+zdCLg0ljeokD/beeD/Gb/rXvrgXS5F/dK
U+zyTGY3Jlga5eSeP9yGkdx34QKBgQDbAdY2rtKJ1UZXNvYsyj4Vz0RGVEABZAnZ
yAA5iOHE4y/GLz/p7hKx6mh4nhQAOq/R+s7kT+IJoJNDrfTLZ6vvjWjGj8TREUBD
+rmp7Xu5flO2VavaDVJY13QvkNJHuCn5F/fPdYJ+DlnBkgxbG6tiP2XeGKNl3ETc
t+8Vak9b5wKBgCHlEFOS2eTsQs45EJTU218HFN31WTtIm78rvyQCZ1SZ4T+F5abi
OwRQpNVKBYQ+veO5ePwL5NIsR8P2NgJ1gKo+xABxcRPoNelFxuK1dLL2CH2Guq4n
9GMcn8349yBKa/82VY1fVN3MwW3YNghk8FrXmv6oE7HHZd1StTfRPRUhAoGAeGjK
HcaAeseHpOvS7U1EVTlfkEtB5YHa6KHNfdSETHucCJpxyWvbW3ZFegtvc7DQeeIr
XduNqz1duhgqPqsBHPPiZ0kkLM4PR1DSQ1sOay3JWLZBheFNJQtQFdbJxiS67vj+
hlFVLXk2X/asVGsiDyJMH4/sRlSUh8WY7W1FtIECgYAEz/TGAepaPpPXADU/x0Hb
zHvzpdxmFnSKzbfCQIxKvby8UOxdSpTU5JthvA7YhZsCwtjvdc6llzDJ1LQ7vwBB
Y535/X/RlouD68o4a4+zz9k3acD7tcHPOGY7wy9ga+e6AJqDz0g5rj+c1ZMd84U/
5EfWxNSIcFqAtFpbPg3VSA==
-----END PRIVATE KEY-----
e un server.crt
-----BEGIN CERTIFICATE-----
MIIDGzCCAgOgAwIBAgIUWd2CIejZK0uuqLPUDTUkA20SyAAwDQYJKoZIhvcNAQEL
BQAwHTEbMBkGA1UEAwwSc2VydmVyb3RhLmRkbnMubmV0MB4XDTIxMDkzMDEyMjkx
MFoXDTIzMDkzMDEyMjkxMFowHTEbMBkGA1UEAwwSc2VydmVyb3RhLmRkbnMubmV0
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxXtdGLH6IZKnpJYCyyI9
PWN6dQe4wM0J85lqF+omjxmHCY4WQfON6PwIxlhnDDGC4pj/2m2wFkIpg1sDyMmS
Ow8Q0c7Zu3nqDk5gptKWtkjQdmOlsa48jAX49fwvb6Fwyh+Hz94lLuCTQi59LG2S
UPuubXh74ct1pKnc8pwgtIuClSSbgcLCGkhAnLbjTJaet4UemgVcUcorlmqwDKbd
VQR+5DL6biVxy+cLJcwKqH93xrj9hh1I4ktWSBpM/xInlGnA1Fa0qtGrM6m+d3uq
E/GL6MpM0kIvTyxyFVk+LlProQS8zNyRU1KAw51EcT5qcj6FkVMVpSodyARoJPwn
BwIDAQABo1MwUTAdBgNVHQ4EFgQUY6S5QEqFZe7UTVQLAuet07qsW6MwHwYDVR0j
BBgwFoAUY6S5QEqFZe7UTVQLAuet07qsW6MwDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAQEAmxHExHoWclbgdhsIenSyb2wihbC7+QO69dAfQPwJOEQ9
9Lb4h6nmJzuHD+ohFsnNDm3cnKadG7F7to7/LdeG4g5qBNzdMVolsgMTMQ0wyFBx
iTxKPh2FGsGvzftJoYLNXAYXDKtrwK7cxn+HOQOqCw7Q2clMRljva42GQJytDsOx
7F6pVNDnDzo2H1Sni7WzwzIQGb06dUinPY4AhunYRaasbWAU4a4K35x2c5IqCrMW
5TYYCt2KMOaTNLbd0Lh9/ImeJnImAVGN8DivXbtNfhrj+Pl8McHnUztUMkcyHMNR
dVRh0YwsiXtZcu+RWatZB2eJQZJyZx04pIAwgIdhBA==
-----END CERTIFICATE-----
Generare un certificato da un file di configurazione
Se vuoi dare maggiori informazioni sul certificato puoi usare questo form del file di configurazione, qui il mio local.cnf
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = IT
ST = Perugia
L = Gubbio
O = www.mischianti.org
OU = dev
CN = 192.168.1.70: Self-signed certificate
[req_ext]
subjectAltName = @alt_names
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = serverota.ddns.net
e il comando diventa
openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout server.key -out server.crt -config local.cnf
I dati più rilevanti sono il DNS.1 che identifica il nome comune alternativo, e utilizzo il nome comune per identificare ciò che voglio fare.
Configurazione del certificato per l’IP locale
Questo tipo di generazione non è supportata per ESP32, ma può essere utilizzata per tutti i programmatori.
Possiamo generare un certificato Self Signed associato all’indirizzo IP, non al nome host, ed è supportato da molti dispositivi, ma non da quelli di Espressif.
Ecco un esempio.
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = IT
ST = Perugia
L = Gubbio
O = www.mischianti.org
OU = dev
CN = 192.168.1.70: Self-signed certificate
[req_ext]
subjectAltName = @alt_names
[v3_req]
subjectAltName = @alt_names
[alt_names]
IP.1 = 192.168.1.70
Aggiungi il certificato al server NodeJS.
Innanzitutto, modifico il server per esporre l’endpoint HTTPS, seleziono la porta 3443 a tale scopo e aggiungo i due certificati alla cartella certificate
.
var fs = require('fs');
const express = require('express');
var http = require('http');
var https = require('https');
var privateKey = fs.readFileSync('certificate/server.key', 'utf8');
var certificate = fs.readFileSync('certificate/server.crt', 'utf8');
var credentials = {key: privateKey, cert: certificate};
const { networkInterfaces } = require('os');
const path = require('path');
const app = express();
var httpServer = http.createServer(app);
var httpsServer = https.createServer(credentials, app);
const nets = networkInterfaces();
// Server port
const PORT = 3000;
const PORT_HTTPS = 3443;
app.get('/', (request, response) => response.send('Hello from www.mischianti.org!'));
let downloadCounter = 1;
const LAST_VERSION = 0.3;
app.get('/update', (request, response) => {
const version = (request.header("x-esp8266-version"))?parseFloat(request.header("x-esp8266-version")):Infinity;
if (version<LAST_VERSION){
// If you need an update go here
response.status(200).download(path.join(__dirname, 'firmware/httpUpdateNew.bin'), 'httpUpdateNew.bin', (err)=>{
if (err) {
console.error("Problem on download firmware: ", err)
}else{
downloadCounter++;
}
});
console.log('Your file has been downloaded '+downloadCounter+' times!')
}else{
response.status(304).send('No update needed!')
}
});
httpServer.listen(PORT, () => {
const results = {}; // Or just '{}', an empty object
for (const name of Object.keys(nets)) {
for (const net of nets[name]) {
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
if (net.family === 'IPv4' && !net.internal) {
if (!results[name]) {
results[name] = [];
}
results[name].push(net.address);
}
}
}
console.log('HTTP Listening on port '+PORT+'\n', results)
});
httpsServer.listen(PORT_HTTPS, () => console.log('HTTPS Listening on port '+PORT_HTTPS+'\n'));
Ora nel prompt dei comandi, hai questo output.
D:\Projects\IdeaProjects\OTAWebPages\OTAServer>node index.js
HTTP Listening on port 3000
{
Ethernet: [ '192.168.1.70' ],
'VirtualBox Host-Only Network': [ '192.168.56.1' ],
'VMware Network Adapter VMnet1': [ '192.168.113.1' ],
'VMware Network Adapter VMnet8': [ '192.168.116.1' ]
}
HTTPS Listening on port 3443
Modificare lo sketch per eseguire una chiamata HTTP sicura
Ci sono due modi per eseguire questo esempio.
Impedire la convalida del certificato
Il primo è impedire qualsiasi convalida del certificato con questo comando:
client.setInsecure();
È necessario utilizzare questo comando senza aggiungere un certificato ma solo se non ti è possibile generare un certificato valido.
Creare e aggiungere CertStore
Il secondo è fidarsi del certificato del server senza utilizzare un’autorità di certificazione, e puoi farlo aggiungendo al dispositivo un certificato valido del server.
In pratica andrai ad aggiungere direttamente al client il certificato necessario.
static const char serverCACert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDGzCCAgOgAwIBAgIUWd2CIejZK0uuqLPUDTUkA20SyAAwDQYJKoZIhvcNAQEL
BQAwHTEbMBkGA1UEAwwSc2VydmVyb3RhLmRkbnMubmV0MB4XDTIxMDkzMDEyMjkx
MFoXDTIzMDkzMDEyMjkxMFowHTEbMBkGA1UEAwwSc2VydmVyb3RhLmRkbnMubmV0
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxXtdGLH6IZKnpJYCyyI9
PWN6dQe4wM0J85lqF+omjxmHCY4WQfON6PwIxlhnDDGC4pj/2m2wFkIpg1sDyMmS
Ow8Q0c7Zu3nqDk5gptKWtkjQdmOlsa48jAX49fwvb6Fwyh+Hz94lLuCTQi59LG2S
UPuubXh74ct1pKnc8pwgtIuClSSbgcLCGkhAnLbjTJaet4UemgVcUcorlmqwDKbd
VQR+5DL6biVxy+cLJcwKqH93xrj9hh1I4ktWSBpM/xInlGnA1Fa0qtGrM6m+d3uq
E/GL6MpM0kIvTyxyFVk+LlProQS8zNyRU1KAw51EcT5qcj6FkVMVpSodyARoJPwn
BwIDAQABo1MwUTAdBgNVHQ4EFgQUY6S5QEqFZe7UTVQLAuet07qsW6MwHwYDVR0j
BBgwFoAUY6S5QEqFZe7UTVQLAuet07qsW6MwDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAQEAmxHExHoWclbgdhsIenSyb2wihbC7+QO69dAfQPwJOEQ9
9Lb4h6nmJzuHD+ohFsnNDm3cnKadG7F7to7/LdeG4g5qBNzdMVolsgMTMQ0wyFBx
iTxKPh2FGsGvzftJoYLNXAYXDKtrwK7cxn+HOQOqCw7Q2clMRljva42GQJytDsOx
7F6pVNDnDzo2H1Sni7WzwzIQGb06dUinPY4AhunYRaasbWAU4a4K35x2c5IqCrMW
5TYYCt2KMOaTNLbd0Lh9/ImeJnImAVGN8DivXbtNfhrj+Pl8McHnUztUMkcyHMNR
dVRh0YwsiXtZcu+RWatZB2eJQZJyZx04pIAwgIdhBA==
-----END CERTIFICATE-----
)EOF";
[...]
WiFiClientSecure client;
// client.setInsecure();
client.setCACert(serverCACert);
// Reading data over SSL may be slow, use an adequate timeout
client.setTimeout(12000 / 1000); // timeout argument is defined in seconds for setTimeout
Impostare l’orologio per la convalida del certificato.
Parlo del servizio NTP in profondità nell’articolo “Protocollo NTP, fuso orario e ora legale (DST) con esp8266, esp32 o Arduino” e useremo questa funzione.
// Set time via NTP, as required for x.509 validation
void setClock() {
configTime(0, 0, "pool.ntp.org", "time.nist.gov"); // UTC
Serial.print(F("Waiting for NTP time sync: "));
time_t now = time(nullptr);
while (now < 8 * 3600 * 2) {
yield();
delay(500);
Serial.print(F("."));
now = time(nullptr);
}
Serial.println(F(""));
struct tm timeinfo;
gmtime_r(&now, &timeinfo);
Serial.print(F("Current time: "));
Serial.print(asctime(&timeinfo));
}
Sketch completo per un aggiornamento sicuro
Adesso andiamo ad assemblare tutta la parte e il risultato è questo.
/*
* ESP32 Basic Sketch for automatic update of firmware at start
* connect to an HTTPS server with self signed certificate
*
* Renzo Mischianti <www.mischianti.org>
*
* https://mischianti.org
*/
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <HTTPUpdate.h>
#include <time.h>
#ifndef APSSID
#define APSSID "<YOUR-SSID>"
#define APPSK "<YOUR-PASSWD>"
#endif
WiFiMulti WiFiMulti;
#define FIRMWARE_VERSION "0.3"
// Set time via NTP, as required for x.509 validation
void setClock() {
configTime(0, 0, "pool.ntp.org", "time.nist.gov"); // UTC
Serial.print(F("Waiting for NTP time sync: "));
time_t now = time(nullptr);
while (now < 8 * 3600 * 2) {
yield();
delay(500);
Serial.print(F("."));
now = time(nullptr);
}
Serial.println(F(""));
struct tm timeinfo;
gmtime_r(&now, &timeinfo);
Serial.print(F("Current time: "));
Serial.print(asctime(&timeinfo));
}
static const char serverCACert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDGzCCAgOgAwIBAgIUWd2CIejZK0uuqLPUDTUkA20SyAAwDQYJKoZIhvcNAQEL
BQAwHTEbMBkGA1UEAwwSc2VydmVyb3RhLmRkbnMubmV0MB4XDTIxMDkzMDEyMjkx
MFoXDTIzMDkzMDEyMjkxMFowHTEbMBkGA1UEAwwSc2VydmVyb3RhLmRkbnMubmV0
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxXtdGLH6IZKnpJYCyyI9
PWN6dQe4wM0J85lqF+omjxmHCY4WQfON6PwIxlhnDDGC4pj/2m2wFkIpg1sDyMmS
Ow8Q0c7Zu3nqDk5gptKWtkjQdmOlsa48jAX49fwvb6Fwyh+Hz94lLuCTQi59LG2S
UPuubXh74ct1pKnc8pwgtIuClSSbgcLCGkhAnLbjTJaet4UemgVcUcorlmqwDKbd
VQR+5DL6biVxy+cLJcwKqH93xrj9hh1I4ktWSBpM/xInlGnA1Fa0qtGrM6m+d3uq
E/GL6MpM0kIvTyxyFVk+LlProQS8zNyRU1KAw51EcT5qcj6FkVMVpSodyARoJPwn
BwIDAQABo1MwUTAdBgNVHQ4EFgQUY6S5QEqFZe7UTVQLAuet07qsW6MwHwYDVR0j
BBgwFoAUY6S5QEqFZe7UTVQLAuet07qsW6MwDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAQEAmxHExHoWclbgdhsIenSyb2wihbC7+QO69dAfQPwJOEQ9
9Lb4h6nmJzuHD+ohFsnNDm3cnKadG7F7to7/LdeG4g5qBNzdMVolsgMTMQ0wyFBx
iTxKPh2FGsGvzftJoYLNXAYXDKtrwK7cxn+HOQOqCw7Q2clMRljva42GQJytDsOx
7F6pVNDnDzo2H1Sni7WzwzIQGb06dUinPY4AhunYRaasbWAU4a4K35x2c5IqCrMW
5TYYCt2KMOaTNLbd0Lh9/ImeJnImAVGN8DivXbtNfhrj+Pl8McHnUztUMkcyHMNR
dVRh0YwsiXtZcu+RWatZB2eJQZJyZx04pIAwgIdhBA==
-----END CERTIFICATE-----
)EOF";
const char* updateServer = "serverota.ddns.net";
const int updateServerPort = 3443;
void setup() {
Serial.begin(115200);
// Serial.setDebugOutput(true);
Serial.println();
Serial.println();
Serial.println();
for (uint8_t t = 4; t > 0; t--) {
Serial.printf("[SETUP] WAIT %d...\n", t);
Serial.flush();
delay(1000);
}
WiFi.mode(WIFI_STA);
WiFiMulti.addAP(APSSID, APPSK);
Serial.print(F("Firmware version "));
Serial.println(FIRMWARE_VERSION);
// WiFi.setDNS(dns1, dns2);
delay(2000);
}
void loop() {
// wait for WiFi connection
if ((WiFiMulti.run() == WL_CONNECTED)) {
setClock();
WiFiClientSecure client;
// client.setInsecure();
client.setCACert(serverCACert);
// Reading data over SSL may be slow, use an adequate timeout
client.setTimeout(12000 / 1000); // timeout argument is defined in seconds for setTimeout
// httpUpdate.setLedPin(LED_BUILTIN, LOW);
httpUpdate.rebootOnUpdate(false); // remove automatic update
Serial.println(F("Update start now!"));
t_httpUpdate_return ret = httpUpdate.update(client, "https://"+String(updateServer)+":"+String(updateServerPort)+"/update", FIRMWARE_VERSION);
switch (ret) {
case HTTP_UPDATE_FAILED:
Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s\n", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str());
Serial.println(F("Retry in 10secs!"));
delay(10000); // Wait 10secs
break;
case HTTP_UPDATE_NO_UPDATES:
Serial.println("HTTP_UPDATE_NO_UPDATES");
break;
case HTTP_UPDATE_OK:
Serial.println("HTTP_UPDATE_OK");
delay(1000); // Wait a second and restart
ESP.restart();
break;
}
delay(10000);
}
}
L’output seriale è lo stesso dell’aggiornamento HTTP.
[SETUP] WAIT 4...
[SETUP] WAIT 3...
[SETUP] WAIT 2...
[SETUP] WAIT 1...
Firmware version 0.1
Waiting for NTP time sync: .
Current time: Thu Sep 30 13:16:54 2021
Update start now!
HTTP_UPDATE_OK
ets Jun 8 2016 00:22:57
rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1216
ho 0 tail 12 room 4
load:0x40078000,len:10944
load:0x40080400,len:6388
entry 0x400806b4
[SETUP] WAIT 4...
[SETUP] WAIT 3...
[SETUP] WAIT 2...
[SETUP] WAIT 1...
Firmware version 0.2
Grazie
- ESP32: piedinatura, specifiche e configurazione dell’Arduino IDE
- ESP32: fileSystem integrato SPIFFS
- ESP32: gestire più seriali e logging per il debug
- ESP32 risparmio energetico pratico
- ESP32 risparmio energetico pratico: gestire WiFi e CPU
- ESP32 risparmio energetico pratico: modem e light sleep
- ESP32 risparmio energetico pratico: deep sleep e ibernazione
- ESP32 risparmio energetico pratico: preservare dati al riavvio, sveglia a tempo e tramite tocco
- ESP32 risparmio energetico pratico: sveglia esterna e da ULP
- ESP32 risparmio energetico pratico: sveglia da UART e GPIO
- ESP32: filesystem integrato LittleFS
- ESP32: filesystem integrato FFat (Fat/exFAT)
- ESP32-wroom-32
- ESP32-CAM
- ESP32: ethernet w5500 con chiamate standard (HTTP) e SSL (HTTPS)
- ESP32: ethernet enc28j60 con chiamate standard (HTTP) e SSL (HTTPS)
- Come usare la scheda SD con l’esp32
- esp32 e esp8266: file system FAT su memoria SPI flash esterna
- Gestione aggiornamenti firmware e OTA
- Gestione del firmware
- Aggiornamento OTA con Arduino IDE
- Aggiornamento OTA con browser web
- Aggiornamenti automatici OTA da un server HTTP
- Aggiornamento del firmware non standard
- Integrare LAN8720 con ESP32 per la connettività Ethernet con plain (HTTP) e SSL (HTTPS)
- Collegare l’EByte E70 (CC1310) ai dispositivi ESP32 c3/s3 ed un semplice sketch di esempio
- ESP32-C3: piedinatura, specifiche e configurazione dell’IDE Arduino
- Integrazione del modulo W5500 su ESP32 con Core 3: supporto nativo ai protocolli Ethernet con SSL e altre funzionalità
- Integrazione del modulo LAN8720 su ESP32 con Core 3: supporto nativo del protocollo Ethernet con SSL e altre funzionalità.
- Dallas DS18B20
- Dallas DS18B20 con ESP32 ed ESP8266: introduzione e modalità parasita
- Dallas DS18B20 con ESP32 ed ESP8266: gate P-MOSFET pull-up e allarmi
- Dallas DS18B20 con ESP32 ed ESP8266: tutte le topologie OneWire, lunghe derivazioni e più dispositivi