ESP32 Requetês HTTPs

De knowledge
Aller à la navigation Aller à la recherche

Généralités

Pour réaliser des requêtes https avec un microcontrôleur ce n'est pas aussi simple que sur un ordinateur (même que sur un Raspberry PI).

En effet le protocole utilise des certificats X.509 et impose de vérifier le certificat du serveur et notamment:

  • Vérifier ses dates de validités (le microcontrôleur DOIT dont connaitre l'heure et le jour ,en tout cas la date)
  • Vérifier que le certificat a été bien signé par la CA dont il se réclame
  • Que son CN corresponds à l'adresse DNS demandée...
  • D'autres choses encore

Un ordinateur a, normalement, la liste des CA auquel il fait confiance (il récupère cette liste avec son OS ou au travers du navigateur). Sous linux c'est dans /etc/ssl/certs. j'ai environ 280 CA acceptées sur mon serveur en ce moment! Ca va de ACCVRAIZ1 à vTrus_ECC_Root_CA!

Sur cette machine j'ai déjà plus de 600ko de certificats. Sur un microcontrôleur nous n'avons tout simplement pas la place. Alors comment faire?

  • Nous allons avoir NOTRE tiers de confiance à nous. Nous nous y tiendrons pour toute la durée de vie du firmware. Bon en général le certificat d'une CA est garanti sur une grande période de temps.

Par exemple QuoVadis_Root_CA_3_G3.pem qui est une des CA sur mon serveur linux est :

openssl x509 -in certs/QuoVadis_Root_CA_3_G3.pem -text -noout
.....
        Validity
            Not Before: Jan 12 20:26:32 2012 GMT
            Not After : Jan 12 20:26:32 2042 GMT
.....

Donc si on conçoit un objet connecté supportant ce certificat on est tranquille jusqu'en... 2042. Il faudra faire un FW update un peu avant si notre objet est encore vivent.

La programmation

Les librairies pour l'environnement ARDUINO

Nous allons écrire le programme avec l'IDE Arduino (oui d'ESP IDF c'est plus compliqué, on y reviendra un jour).

La connexion au Wifi se fera avec les classiques :

#include <WiFi.h>
#include <WiFiMulti.h>

On utilisera la librairie qui facilite les requêtes HTTP:

#include <HTTPClient.h>

Et enfin la librairie qui gère le HTTPS.

#include <WiFiClientSecure.h>

Inclure le certificat

Comme vu plus haut il va falloir définit le certificat de notre CA dans le code. On le fait avec une simple chaine de caractère.

Si notre certificat est :

-----BEGIN CERTIFICATE-----
MIICzTCCAbWgAwIBAgIUSUlscEexW4HYbr0Dhnm8jNnb5vEwDQYJKoZIhvcNAQEL
BQAwEDEOMAwGA1UEAwwFbGlmb3UwHhcNMjMwNTE2MTQ0NTU2WhcNMzMwNTEzMTQ0
NTU2WjAQMQ4wDAYDVQQDDAVsaWZvdTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBALJhjEJHcVhr8g8nTIfJZNJOfv+HL1Cqudn1EAlBg7lES/Ub5qeI+u1V
H18xljNQtSr+jE3PG1R+La+adUObj3HQoWMKZETIoBM0lWZLZsDcOYhZv6ODHz0o
MTUnJTfVMYj2i8Twp3AQEgDnYCS0KszAZ7kmnKQms9SEzeyVlFpnJG4AHLIe0MUk
JqpYKyiJ2nEMJ66xBmcFfwCuzCArayz8qxPOjvSRvBWSC0lMzNpx8NESf8P0Q+FC
w7Jk1E7Lm7vOJ9Nn84VrAVMAceebWLnGAJ/TnVheJsj+4yrHyMsfIVY9s/hNzARP
8Et9tVXOhc1ZvpapLsPfRNknSGpJIBMCAwEAAaMfMB0wCQYDVR0TBAIwADAQBgNV
HREECTAHggVsaWZvdTANBgkqhkiG9w0BAQsFAAOCAQEAkIUxRbQ2dpxxHpY7kFhr
GucTEAbezUlcR0dVE2qKjt0D1Q0zHYvl08UlreFMMoX1ElkMVIfY2W0m9E6EEEj6
cqswMoH1TY2D/uuVASLJZaGY63kFaReTmebhDrn8hGUkLKA+LpGN6ZgejCVRa9mD
wPi13eNFbXEFPPptEH1AymQSyEyP11vrlSaUtHAIO2Kn2h+gokUBxHllz/OBYWej
X/yvWp7wLQWPuizEGPUbCLnvTIg4koQpo2+UUVObCESTwQ39VkKCll4tFZL/qeOV
cjSqjoR7wQzMfvCEHbPypKVdgS+DdvmHakkLs7tTA7ZvOp1xOaxCxhvC+12CA0vb
bQ==
-----END CERTIFICATE-----

Il faudra l'intégrer dans un char* :

const char* rootCACertificate = \
"-----BEGIN CERTIFICATE-----\n" \
"MIICzTCCAbWgAwIBAgIUSUlscEexW4HYbr0Dhnm8jNnb5vEwDQYJKoZIhvcNAQEL\n" \
"BQAwEDEOMAwGA1UEAwwFbGlmb3UwHhcNMjMwNTE2MTQ0NTU2WhcNMzMwNTEzMTQ0\n" \
"NTU2WjAQMQ4wDAYDVQQDDAVsaWZvdTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\n" \
"AQoCggEBALJhjEJHcVhr8g8nTIfJZNJOfv+HL1Cqudn1EAlBg7lES/Ub5qeI+u1V\n" \
"H18xljNQtSr+jE3PG1R+La+adUObj3HQoWMKZETIoBM0lWZLZsDcOYhZv6ODHz0o\n" \
"MTUnJTfVMYj2i8Twp3AQEgDnYCS0KszAZ7kmnKQms9SEzeyVlFpnJG4AHLIe0MUk\n" \
"JqpYKyiJ2nEMJ66xBmcFfwCuzCArayz8qxPOjvSRvBWSC0lMzNpx8NESf8P0Q+FC\n" \
"w7Jk1E7Lm7vOJ9Nn84VrAVMAceebWLnGAJ/TnVheJsj+4yrHyMsfIVY9s/hNzARP\n" \
"8Et9tVXOhc1ZvpapLsPfRNknSGpJIBMCAwEAAaMfMB0wCQYDVR0TBAIwADAQBgNV\n" \
"HREECTAHggVsaWZvdTANBgkqhkiG9w0BAQsFAAOCAQEAkIUxRbQ2dpxxHpY7kFhr\n" \
"GucTEAbezUlcR0dVE2qKjt0D1Q0zHYvl08UlreFMMoX1ElkMVIfY2W0m9E6EEEj6\n" \
"cqswMoH1TY2D/uuVASLJZaGY63kFaReTmebhDrn8hGUkLKA+LpGN6ZgejCVRa9mD\n" \
"wPi13eNFbXEFPPptEH1AymQSyEyP11vrlSaUtHAIO2Kn2h+gokUBxHllz/OBYWej\n" \
"X/yvWp7wLQWPuizEGPUbCLnvTIg4koQpo2+UUVObCESTwQ39VkKCll4tFZL/qeOV\n" \
"cjSqjoR7wQzMfvCEHbPypKVdgS+DdvmHakkLs7tTA7ZvOp1xOaxCxhvC+12CA0vb\n" \
"bQ==\n" \
"-----END CERTIFICATE-----\n";

Où trouver le certificat de la CA voulue.

Si on est notre propre certificateur c'est simple on connais notre certificat racine c'est ... nous.

Sinon comment faire?

On part de l'URL qu'on va souhaiter requêter : dans mon exemple ce sera https://jigsaw.w3.org/HTTP/connection.html En fait il s'agit de l'exemple donné avec la librairie WifiSecureClient.h.

On remarquera d'ailleurs qu'avec l'exemple le certificat inclus ne convient pas. Non pas qu'elle soit échue mais le site jigsaw.w3.org à décidé de changer de CA. D'où l'idée de bien choisir sa CA et de maitriser les achats pour le renouvellement de son certificat.

Si on va sur https://jigsaw.w3.org/HTTP/connection.html avec Google Chrome la page s'affiche sans probmlèmes :

Jigsaw.w3.org.png

Le navigateur ne nous signale pas de problèmes :

La bare d'adresse indique :

ADDRESSBAR.png

Le petit cadenas indique une connexion sécurisée.

La même chose que pour une banque:

URL BNP.png

Comment connaitre la CA?

On clique sur le cadenas en question qui ouvre le menu suivant:

Chrome Menu Cadenas.png

On clique sur "La connexion est sécurisée".

Certificat Valide.png

Puis sur certificat valide.

Et enfin sur l'onglet détails

Détails de certifiact.png

Ce qui nous intéresse c'est la hiérarchie des certificats. On va donner à notre firmware la clé qui lui permetra de valider le certificat envoyé.

On voit là que le certificat du serveur lui-même est donné par "cloud fare" dont on a le certificat (Cloudfare Inc EEC CA-3).

Cette CA n'étant pas connu de tous le monde elle s'est elle même faite certifier par un certificateur dit "racine" (root) .

Cette CA Racine est Baltimore CyberTrust Root. Puisque Chrome l'a correctement détecté c'est que cette CA racine est bien reconnue par Google. Si on regarde dans le serveur linux ci-dessus il y a également un certificat "Baltimore_CyberTrust_Root.pem"

Sur Chrome on clique sur le "root CA" puis "export" afin de sauvegarder le fichier quelquepart.

Ou trouve :

-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----

Regardeons ce qu'il y a dans : /etc/ssl/certs/Baltimore_CyberTrust_Root.pem

On constate que c'est exactement le même. Et c'est heureux.

Notre navigateur Chrome incorpore la même CA qu'un Linux Debian standard. Ca confirme l'explication ci dessus concernant l'universalité des CA.

On transforme ce CA en chaine de caractères:

const char* rootCACertificate = \
"-----BEGIN CERTIFICATE-----\n" \
"MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ\n" \
"RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD\n" \
"VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX\n" \
"DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y\n" \
"ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy\n" \
"VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr\n" \
"mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr\n" \
"IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK\n" \
"mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu\n" \
"XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy\n" \
"dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye\n" \
"jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1\n" \
"BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3\n" \
"DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92\n" \
"9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx\n" \
"jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0\n" \
"Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz\n" \
"ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS\n" \
"R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp\n" \
"-----END CERTIFICATE-----\n";

Le mieux serait de le mettre dans un fichier include "CA-Cert.h" pour faciliter la maintenance.

Il est a noter que n'importe quel certificat de la chaine peut être utilisé.

  • Plus on remonte vers le CA plus c'est sécurisé et stable (date d'expiration ici à 2025 on a quelques années pour prendre une décision c'est peu)
  • Plus on remonte vers la CA plus la validation est longue.

Le code complet

#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include "CA-certificat.h"
#include "SSIDs.h"

void setClock() {
  configTime(0, 0, "pool.ntp.org");

  Serial.print(F("Waiting for NTP time sync: "));
  time_t nowSecs = time(nullptr);
  while (nowSecs < 8 * 3600 * 2) {
    delay(500);
    Serial.print(F("."));
    yield();
    nowSecs = time(nullptr);
  }

  Serial.println();
  struct tm timeinfo;
  gmtime_r(&nowSecs, &timeinfo);
  Serial.print(F("Current time: "));
  Serial.print(asctime(&timeinfo));
}


WiFiMulti WiFiMulti;

void setup() {

  Serial.begin(115200);

  Serial.println();
  Serial.println();
  Serial.println();

  WiFi.mode(WIFI_STA);
  WiFiMulti.addAP(PRIMARY_SSID);
  #ifdef SECONDARY_SSID
    WiFiMulti.addAP(SECONDARY_SSID);
  #endif

  // wait for WiFi connection
  Serial.print("Waiting for WiFi to connect...");
  while ((WiFiMulti.run() != WL_CONNECTED)) {
    Serial.print(".");
  }
  Serial.println(" connected");

  setClock();  
}

void loop() {
  WiFiClientSecure *client = new WiFiClientSecure;
  if(client) {
    client -> setCACert(rootCACertificate);

    {
      HTTPClient https;
  
      Serial.print("[HTTPS] begin...\n");
      if (https.begin(*client, "https://jigsaw.w3.org/HTTP/connection.html")) {  // HTTPS
        Serial.print("[HTTPS] GET...\n");
        // start connection and send HTTP header
        int httpCode = https.GET();
  
        // httpCode will be negative on error
        if (httpCode > 0) {
          // HTTP header has been send and Server response header has been handled
          Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
  
          // file found at server
          if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
            String payload = https.getString();
            Serial.println(payload);
          }
        } else {
          Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
        }
  
        https.end();
      } else {
        Serial.printf("[HTTPS] Unable to connect\n");
      }

      // End extra scoping block
    }
  
    delete client;
  } else {
    Serial.println("Unable to create client");
  }

  Serial.println();
  Serial.println("Waiting 30s before the next round...");
  delay(30000);
}

Les deux #includes

#include "CA-certificat.h"
#include "SSIDs.h"

Ne sont là que pour externaliser :

  • Le certificat racine (voir plus haut)
  • Le SSID du Wifi auquel se connecter (et éventuellement un backup)

Ca ressemblera à cela:

#define PRIMARY_SSID   "MABOX", "aHy678gfzM890nnb65Abhy"
#define SECONDARY_SSID "MABOX-SEC", "aHy678gfzM890nnb65xxxxxxxxx"