esp8266 auto aggiornamenti OTA in HTTPS (SSL/TLS) con certificato autofirmato attendibile – 3

Spread the love

L’aggiornamento OTA (Over the Air) è il processo di caricamento del firmware su un modulo ESP utilizzando una connessione Wi-Fi anziché una porta seriale. Tale funzionalità diventa estremamente utile in caso di accesso fisico limitato o nullo al modulo.

esp8266 self OTA update firmware in HTTPS SSL TLS with trusted self signed certificate
esp8266 self OTA update firmware in HTTPS SSL TLS with a trusted self-signed certificate

Gli aggiornamenti OTA possono essere effettuati utilizzando:

  • Arduino IDE
  • Web Browser
  • Server HTTP

Prima di tutto leggi il tutorial “esp8266: flash del firmware binario(.bin) compilato e firmato”.

In questo articolo, spiegheremo l’aggiornamento OTA tramite connessione protetta HTTPS e con un certificato autofirmato valido e attendibile dal client.

Aggiornamento dell’endpoint con il controllo della versione e una connessione TLS sicura

Ora possiamo aggiornare il nostro esempio con la connessione SSL/TLS.

Generazione di certificati autofirmati

Se desideri utilizzare un certificato autofirmato, puoi utilizzare OpenSSL per generare chiavi e certificati privati.

Puoi ottenere OpenSSL scaricando la versione Linux da tutti i gestori di pacchetti o scaricando per Windows da qui. Per usarlo con più semplicità, aggiungi a bin pah.

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 una chiave 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 autofirmato valido

Se non hai bisogno di creare un ambiente autentico, vai al paragrafo successivo, i certificati generati nel capitolo precedente sono sufficienti per il nostro test.

Prima di tutto, dobbiamo assegnare un nome all’host del server, ci sono molte alternative, ma penso che il modo più veloce sia usare un DNS dinamico.

Comunemente il DNS dinamico viene utilizzato per assegnare dinamicamente un nome all’IP fornitoci dall’ISP, ma è possibile impostare un IP statico e utilizzarlo come un tipico host; ecco un esempio con un servizio gratuito NO-IP.

Configura il servizio DNS dinamico

Vai a https://www.noip.com/ e Registrati.

Aggiungo la mia email, inserisco una password e creo un nome come serverota.ddns.net, quindi clicco su Registrazione gratuita .

NOIP sign up and generate dynamic dns entry
NOIP sign up and generate dynamic DNS entry

Dopo l’e-mail e la conferma dell’iscrizione, vai a questo link https://my.noip.com/dynamic-dns e puoi vedere l’elenco delle voci DNS dinamiche.

NOIP list of dynamic dns entry
NOIP list of dynamic DNS entry

Ora clicca su modifica e inserisci l’indirizzo IP del tuo server nodejs; per me ora è 192.168.1.70.

NOIP modify dynamic dns entry for local LAN IP address
NOIP modify dynamic DNS entry for local LAN IP address

Applicare e attendere qualche minuto. Dopodiché (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 nome.

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

Genera certificato autofirmato

È possibile utilizzare il comando base già utilizzato modificando il nome comune.

openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout server.key -out server.crt  -subj "/CN=serverota.ddns.net"

E 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-----

Genera certificato con un file di configurazione

Se vuoi dare maggiori informazioni sul certificato, puoi utilizzare 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

Il dato più rilevante è il DNS.1 che identifica il nome comune alternativo e io uso un termine familiare per identificare cosa voglio fare.

Configurazione del certificato per l’IP locale

Questa generazione non è supportata dall’ESP32 ma può essere utile per tutti i programmatori.

Possiamo generare un certificato autofirmato legato all’indirizzo IP, non all’hostname, e molti dispositivi lo supportano, ma non quelli di Espressif.

Ecco l’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

Aggiungere 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

Modifica lo sketch per eseguire una chiamata HTTP sicura.

Ci sono due modi per eseguire questo esempio.

Impedisci la convalida del certificato

Il primo è impedire qualsiasi convalida del certificato con questo comando

     client.setInsecure();

o meglio il comando più specifico.

    client.allowSelfSignedCerts();

Se non voui aggiungere un certificato, è necessario utilizzare questo comando o se non è possibile generare un certificato valido.

Crea e aggiungi il CertStore

Il secondo è rendere fidato il certificato del server senza utilizzare un’autorità di certificazione; puoi farlo aggiungendo al dispositivo un certificato valido del server.

In pratica aggiungerai 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";

[...]

    BearSSL::WiFiClientSecure client;

    bool mfln = client.probeMaxFragmentLength(updateServer, updateServerPort, 1024);  // server must be the same as in ESPhttpUpdate.update()
    Serial.printf("MFLN supported: %s\n", mfln ? "yes" : "no");
    if (mfln) {
      client.setBufferSizes(1024, 1024);
    }

//    client.allowSelfSignedCerts();

    BearSSL::X509List x509(serverCACert);
    client.setTrustAnchors(&x509);

    setClock();

Impostare l’orologio per la convalida del certificato

Parlo del servizio NTP in modo approfondito in questo 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
time_t setClock() {
  configTime(2*3600, 0, "pool.ntp.org", "time.nist.gov");

  Serial.print("Waiting for NTP time sync: ");
  time_t now = time(nullptr);
  while (now < 8 * 3600 * 2) {
    delay(500);
    Serial.print(".");
    now = time(nullptr);
  }
  Serial.println("");
  struct tm timeinfo;
  gmtime_r(&now, &timeinfo);
  Serial.print("Current time: ");
  Serial.print(asctime(&timeinfo));
  return now;
}

Sketch completo per un aggiornamento sicuro

Ora assembleremo tutte le parti e il risultato è questo.

/**
 * Sketch with secure and conditionally firmware update
 * with trusted certification authority
 *
 * Renzo Mischianti <www.mischianti.org>
 *
 * https://mischianti.org
 */

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>

#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>

#include <time.h>

#ifndef APSSID
#define APSSID "<YOUR-SSID>"
#define APPSK  "<YOUR-PASSWD>"
#endif

ESP8266WiFiMulti WiFiMulti;

// Set time via NTP, as required for x.509 validation
time_t setClock() {
  configTime(2*3600, 0, "pool.ntp.org", "time.nist.gov");

  Serial.print("Waiting for NTP time sync: ");
  time_t now = time(nullptr);
  while (now < 8 * 3600 * 2) {
    delay(500);
    Serial.print(".");
    now = time(nullptr);
  }
  Serial.println("");
  struct tm timeinfo;
  gmtime_r(&now, &timeinfo);
  Serial.print("Current time: ");
  Serial.print(asctime(&timeinfo));
  return now;
}

void update_started() {
  Serial.println("CALLBACK:  HTTP update process started");
}

void update_finished() {
  Serial.println("CALLBACK:  HTTP update process finished");
}

void update_progress(int cur, int total) {
  Serial.printf("CALLBACK:  HTTP update process at %d of %d bytes...\n", cur, total);
}

void update_error(int err) {
  Serial.printf("CALLBACK:  HTTP update fatal error code %d\n", err);
}

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";

#define FIRMWARE_VERSION "0.1"

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);
}

const char* updateServer = "serverota.ddns.net";
const int updateServerPort = 3443;

void loop() {
  // wait for WiFi connection
  if ((WiFiMulti.run() == WL_CONNECTED)) {

    BearSSL::WiFiClientSecure client;

    bool mfln = client.probeMaxFragmentLength(updateServer, updateServerPort, 1024);  // server must be the same as in ESPhttpUpdate.update()
    Serial.printf("MFLN supported: %s\n", mfln ? "yes" : "no");
    if (mfln) {
      client.setBufferSizes(1024, 1024);
    }

//    client.allowSelfSignedCerts();

    BearSSL::X509List x509(serverCACert);
    client.setTrustAnchors(&x509);

    setClock();

    // The line below is optional. It can be used to blink the LED on the board during flashing
    // The LED will be on during download of one buffer of data from the network. The LED will
    // be off during writing that buffer to flash
    // On a good connection the LED should flash regularly. On a bad connection the LED will be
    // on much longer than it will be off. Other pins than LED_BUILTIN may be used. The second
    // value is used to put the LED on. If the LED is on with HIGH, that value should be passed
    ESPhttpUpdate.setLedPin(LED_BUILTIN, LOW);

    // Add optional callback notifiers
    ESPhttpUpdate.onStart(update_started);
    ESPhttpUpdate.onEnd(update_finished);
    ESPhttpUpdate.onProgress(update_progress);
    ESPhttpUpdate.onError(update_error);

    ESPhttpUpdate.rebootOnUpdate(false); // remove automatic update

    Serial.println(F("Update start now!"));

     t_httpUpdate_return ret = ESPhttpUpdate.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", ESPhttpUpdate.getLastError(), ESPhttpUpdate.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");
        Serial.println("Your code is up to date!");
          delay(10000); // Wait 10secs
        break;

      case HTTP_UPDATE_OK:
        Serial.println("HTTP_UPDATE_OK");
        delay(1000); // Wait a second and restart
        ESP.restart();
        break;
    }
  }
}

L’output seriale è lo stesso dell’aggiornamento HTTP.

[SETUP] WAIT 4...
[SETUP] WAIT 3...
[SETUP] WAIT 2...
[SETUP] WAIT 1...
Firmware version 0.1
MFLN supported: yes
Waiting for NTP time sync: 
Current time: Thu Sep 30 14:38:12 2021
Update start now!
CALLBACK:  HTTP update process started
CALLBACK:  HTTP update process at 0 of 420432 bytes...
CALLBACK:  HTTP update process at 0 of 420432 bytes...
CALLBACK:  HTTP update process at 4096 of 420432 bytes...
CALLBACK:  HTTP update process at 8192 of 420432 bytes...
CALLBACK:  HTTP update process at 12288 of 420432 bytes...
CALLBACK:  HTTP update process at 16384 of 420432 bytes...
CALLBACK:  HTTP update process at 20480 of 420432 bytes...
CALLBACK:  HTTP update process at 24576 of 420432 bytes...
CALLBACK:  HTTP update process at 28672 of 420432 bytes...
CALLBACK:  HTTP update process at 32768 of 420432 bytes...
CALLBACK:  HTTP update process at 36864 of 420432 bytes...
CALLBACK:  HTTP update process at 40960 of 420432 bytes...
CALLBACK:  HTTP update process at 45056 of 420432 bytes...
CALLBACK:  HTTP update process at 49152 of 420432 bytes...
CALLBACK:  HTTP update process at 53248 of 420432 bytes...
CALLBACK:  HTTP update process at 57344 of 420432 bytes...
CALLBACK:  HTTP update process at 61440 of 420432 bytes...
CALLBACK:  HTTP update process at 65536 of 420432 bytes...
CALLBACK:  HTTP update process at 69632 of 420432 bytes...
CALLBACK:  HTTP update process at 73728 of 420432 bytes...
CALLBACK:  HTTP update process at 77824 of 420432 bytes...

[...]

CALLBACK:  HTTP update process at 393216 of 420432 bytes...
CALLBACK:  HTTP update process at 397312 of 420432 bytes...
CALLBACK:  HTTP update process at 401408 of 420432 bytes...
CALLBACK:  HTTP update process at 405504 of 420432 bytes...
CALLBACK:  HTTP update process at 409600 of 420432 bytes...
CALLBACK:  HTTP update process at 413696 of 420432 bytes...
CALLBACK:  HTTP update process at 417792 of 420432 bytes...
CALLBACK:  HTTP update process at 420432 of 420432 bytes...
CALLBACK:  HTTP update process at 420432 of 420432 bytes...
CALLBACK:  HTTP update process at 420432 of 420432 bytes...
CALLBACK:  HTTP update process finished
HTTP_UPDATE_OK
 
 ets Jan  8 2013,rst cause:2, boot mode:(3,6)

load 0x4010f000, len 3460, room 16 
tail 4
chksum 0xcc
load 0x3fff20b8, len 40, room 4 
tail 4 
chksum 0xc9
csum 0xc9
v00078180 
@cp:B0
ld
 


[SETUP] WAIT 4...
[SETUP] WAIT 3...
[SETUP] WAIT 2...
[SETUP] WAIT 1...
Firmware version 0.2
MFLN supported: yes
Waiting for NTP time sync: 
Current time: Thu Sep 30 14:38:44 2021
Update start now!
HTTP_UPDATE_NO_UPDATES
Your code is up to date!

Grazie

  1. Firmware and OTA update

Spread the love

2 Risposte

  1. Matteo ha detto:

    Buonasera, volevo chiedere se l’aggiornamento ota per esp8266 è fattibile con un server remoto e non nella stessa rete? sto cercando di riprodurre questi tutorial e ho queste domande. Poi, è fattibile fare a ciclico, prima il download del firmware e poi del file system? se si come si potrebbe? grazie

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *