Przy prostych projektach które będą porozumiewać się z serwerami nad którymi mamy pełną kontrolę można wykorzystać odcisk klucza SSL zapisany bezpośrednio w kodzie aplikacji do weryfikacji serwera. Kiedy chcemy by nasze urządzenie zaczęło komunikować się z innymi sewerami i nie przestało działać kiedy zostanie na nich zmieniony certyfikat konieczne jest skorzystanie z jakieś metody weryfikacji certyfikatów. W tym celu wykorzystamy BearSSL i listę certyfikatów Mozilli.

Pobieranie listy certyfikatów Mozilli

Skorzystać możemy ze skryptu który utworzy plik certs.ar https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/BearSSL_CertStore/certs-from-mozilla.py

#!/usr/bin/python3

# This script pulls the list of Mozilla trusted certificate authorities
# from the web at the "mozurl" below, parses the file to grab the PEM
# for each cert, and then generates DER files in a new ./data directory
# Upload these to a SPIFFS filesystem and use the CertManager to parse
# and use them for your outgoing SSL connections.
#
# Script by Earle F. Philhower, III.  Released to the public domain.
from __future__ import print_function
import csv
import os
import sys
from subprocess import Popen, PIPE, call
try:
    from urllib.request import urlopen
except:
    from urllib2 import urlopen
try:
    from StringIO import StringIO
except:
    from io import StringIO

# Mozilla's URL for the CSV file with included PEM certs
mozurl = "https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReportPEMCSV"

# Load the manes[] and pems[] array from the URL
names = []
pems = []
response = urlopen(mozurl)
csvData = response.read()
if sys.version_info[0] > 2:
    csvData = csvData.decode('utf-8')
csvFile = StringIO(csvData)
csvReader = csv.reader(csvFile)
for row in csvReader:
    names.append(row[0]+":"+row[1]+":"+row[2])
    pems.append(row[30])
del names[0] # Remove headers
del pems[0] # Remove headers

# Try and make ./data, skip if present
try:
    os.mkdir("data")
except:
    pass

derFiles = []
idx = 0
# Process the text PEM using openssl into DER files
for i in range(0, len(pems)):
    certName = "data/ca_%03d.der" % (idx);
    thisPem = pems[i].replace("'", "")
    print(names[i] + " -> " + certName)
    ssl = Popen(['openssl','x509','-inform','PEM','-outform','DER','-out', certName], shell = False, stdin = PIPE)
    pipe = ssl.stdin
    pipe.write(thisPem.encode('utf-8'))
    pipe.close()
    ssl.wait()
    if os.path.exists(certName):
        derFiles.append(certName)
        idx = idx + 1

if os.path.exists("data/certs.ar"):
    os.unlink("data/certs.ar");

arCmd = ['ar', 'q', 'data/certs.ar'] + derFiles;
call( arCmd )

for der in derFiles:
    os.unlink(der)

Skrypt utworzy katalog data i w nim zapisze plik certs.ar

Przykładowy szkic korzystający z tego rozwiąznia:

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <CertStoreBearSSL.h>
#include <time.h>
#include <FS.h>

BearSSL::CertStore certStore;

const char* ssid = "SSID";
const char* password = "PASSWD";

void setup() {
  SPIFFS.begin();
  Serial.begin(115200);
  while(!Serial);

  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi .");
  while(WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(100);
  }
  Serial.println();
  Serial.println("Connected!");

  setClock();

  int numCerts = certStore.initCertStore(SPIFFS, PSTR("/certs.idx"), PSTR("/certs.ar"));
  Serial.printf("Number of CA certs read: %d\n", numCerts);
  if (numCerts == 0) {
    Serial.printf("No certs found. Did you run certs-from-mozilla.py and upload the SPIFFS directory before running?\n");
    return; // Can't connect to anything w/o certs!
  }

}

HTTPClient http;

void loop() {
  BearSSL::WiFiClientSecure *bear = new BearSSL::WiFiClientSecure();
  // Integrate the cert store with this connection
  bear->setCertStore(&certStore);

  http.begin(dynamic_cast<WiFiClient&>(*bear), "www.przykladowaStrona.org");

  int httpCode = http.GET();

  if(httpCode > 0) {
    String payload = http.getString();
    Serial.println(httpCode);
    Serial.println(payload);
  }
  else {
    Serial.println("Cos poszło nie tak jak nalezy...");
  }

  http.end();

  delay(100000000);
}

void setClock() {
  configTime(3 * 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));
}

Uwagi końcowe

ESP korzystający z WiFi, pamięci flash i wykonujący obliczenia może mieć duże chwilowe zapotrzebowanie na prąd. By uniknąć problemów w działaniu może zajść potrzeba by zasilić mikrokontroler z czegoś wydajnieszego prądowo niż programator USB-UART.

Poprzedni wpis Następny wpis