14.4.5. Provjera javnog poslužitelja (kamera kao klijent)

Sve sa prethodne stranice o klijentu koji „već ima korijenski certifikat” vrijedi za preglednike, telefone i osobna računala – ali ne vrijedi za kameru. MicroPython-ov ssl dolazi bez ugrađene pohrane povjerenja: svježe flashana kamera ne vjeruje nijednom CA-u, a zadana postavka (ssl.CERT_NONE) ne provjerava ništa i potpuno je otvorena za napad posrednika (man-in-the-middle). Stoga, kada je kamera klijent koji se povezuje s javnim TLS poslužiteljem (HTTPS API, MQTT broker, …) i želite da ona doista provjeri taj poslužitelj, morate sami osigurati sidro povjerenja.

Mehanika je ista kao u primjeru samopotpisanog klijenta na Samopotpisani certifikati; jedina je razlika u tome što je datoteka koju učitavate stvarni CA certifikat umjesto vlastitog certifikata druge strane:

  1. Dohvatite CA certifikat koji sidri lanac poslužitelja. „Sidri” znači certifikat na (ili blizu) vrha poslužiteljevog lanca koji odaberete kao svoju početnu točku povjerenja. TLS poslužitelj šalje svoj listni certifikat i obično svoje posredne certifikate; nikada ne šalje svoj korijenski certifikat. Morate sami pribaviti to sidro povjerenja i to neovisno o poslužitelju – jednostavno vjerovanje bilo čemu što poslužitelj pošalje poništilo bi cijelu svrhu provjere.

    Najprije saznajte koji je CA zapravo izdao poslužiteljev certifikat. Na primjer, za openmv.io

    openssl s_client -connect openmv.io:443 -showcerts < /dev/null
    

    Blok Certificate chain navodi svaki certifikat s njegovim subjektom (s:) i izdavateljem (i:); noviji OpenSSL također ispisuje retke a: (vrsta ključa) i v: (valjanost) koje ovdje možete zanemariti:

    Certificate chain
     0 s:CN=openmv.io
       i:C=US, O=Let's Encrypt, CN=E8
     1 s:C=US, O=Let's Encrypt, CN=E8
       i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
    

    Unos 0 je listni certifikat (openmv.io), koji je izdao posredni certifikat E8. Unos 1 je taj posredni certifikat, koji je izdao korijenski certifikat ISRG Root X1. Izdavatelj (i:) najgornjeg unosa imenuje korijenski certifikat – ovdje ISRG Root X1. (Posredni certifikat je E8 umjesto R10 / R11 koje ste možda vidjeli drugdje jer openmv.io koristi ECDSA certifikat; Let’s Encrypt potpisuje ECDSA listne certifikate svojim posrednim certifikatima serije E, a RSA listne certifikate onima serije R. Oba se lančano vežu na ISRG Root X1.)

    OpenSSL također ispisuje retke depth= i može prijaviti korijenski certifikat s Verification: OK. To se događa samo zato što vaše osobno računalo već vjeruje certifikatu ISRG Root X1 – poslužitelj ga nije poslao (poslužitelj nikada ne šalje svoj korijenski certifikat), a kamera, koja nema pohranu povjerenja, neće ga imati. Upravo zato ga morate sami osigurati.

    Preuzmite taj korijenski certifikat s CA-ovih vlastitih objavljenih korijenskih certifikata. Let’s Encrypt katalogizira sve svoje na stranici s certifikatima Let’s Encrypt; izravna datoteka za ISRG Root X1 je isrgrootx1.pem (također ga nude unaprijed kodiran kao isrgrootx1.der). Drugi CA-ovi objavljuju svoje na sličnoj stranici „root certificates” / „repository”; kanonski javni skup je Mozilla CA program (CCADB). Potvrdite da ste dohvatili pravu datoteku usporedbom njezinog otiska s vrijednošću koju CA objavljuje (dodajte -inform DER ako ste preuzeli .der):

    openssl x509 -in isrgrootx1.pem -noout -subject -fingerprint -sha256
    

    Ako ne želite pratiti korijenski certifikat, umjesto toga možete kopirati posredni certifikat ravno iz izlaza -showcerts (drugi blok -----BEGIN CERTIFICATE-----), vjerovati njemu i prihvatiti da ga morate osvježavati svaki put kada CA rotira posredni certifikat – daleko češće od korijenskog (pogledajte kompromis u nastavku).

  2. Pretvorite ga u DER, točno kao i prije:

    openssl x509 -in isrgrootx1.pem -outform DER -out ca.der
    
  3. Kopirajte ca.der na kameru (datotečni sustav ili ROMFS) i učitajte ga kao sidro povjerenja:

    import socket
    import ssl
    import ntptime
    
    ntptime.settime()                  # validity check needs the clock
    
    addr = socket.getaddrinfo("api.example.com", 443)[0][-1]
    sock = socket.socket()
    sock.connect(addr)
    
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    ctx.verify_mode = ssl.CERT_REQUIRED
    ctx.load_verify_locations(cafile="ca.der")
    ssock = ctx.wrap_socket(sock, server_hostname="api.example.com")
    

    server_hostname je ovdje obavezan: on pokreće SNI i ime je koje se provjerava u odnosu na subjectAltName poslužiteljevog certifikata.

Savjet

Prečac za uobičajeni slučaj. Let’s Encrypt je najčešće korišteni javni CA, a i njegovi RSA i ECDSA certifikati trenutno se lančano vežu na ISRG Root X1 (kao što gornji primjer openmv.io pokazuje). Ako poslužitelji s kojima vaša kamera komunicira koriste Let’s Encrypt, možete u potpunosti preskočiti inspekciju: samo stavite isrgrootx1.der na kameru i izvršite load_verify_locations nad njim.

Ovo ne omogućuje rad TLS-a sa svakom lokacijom. Poslužitelj čiji certifikat dolazi od drugog CA-a (DigiCert, Google Trust Services, Amazon, Sectigo, …) i dalje neće proći provjeru, a budući da kamera vjeruje jednom DER certifikatu po ssl.SSLContext, ne možete spojiti svaki korijenski certifikat onako kako to radi preglednik. Kada ste u nedoumici, identificirajte stvarni CA poslužitelja kako je gore prikazano i vjerujte tom korijenskom certifikatu.

Kojem certifikatu vjerujete je kompromis:

  • Korijenski certifikat (preporučeno). Dugog vijeka trajanja – često desetljećima – pa se ca.der rijetko mijenja. Zahtijeva od poslužitelja da pošalje svoj posredni certifikat kako bi mbedTLS mogao izgraditi put listni → posredni → vaš pouzdani korijenski; praktički svaki ispravno konfiguriran javni poslužitelj to radi.

  • Posredni certifikat. Također radi i nastavlja raditi čak i ako poslužitelj izostavi posredni certifikat, ali posredni se certifikati rotiraju daleko češće od korijenskih, pa ćete morati osvježavati ca.der češće.

  • Sam listni certifikat (pričvršćivanje certifikata, pinning). Najstrože, ali listni se certifikat mijenja pri svakoj obnovi – otprilike svakih 90 dana za Let’s Encrypt – pa ovo ima smisla samo kada i sami kontrolirate poslužitelj i možete usklađeno proslijediti novi pin svakoj kameri. Upravo to radi primjer samopotpisanog klijenta.

Napomena

ssl.SSLContext.load_verify_locations() prima jedan DER kodirani CA certifikat, pa kamera vjeruje točno jednom sidru istovremeno. Da biste dosegnuli poslužitelje pod različitim CA-ovima, koristite zaseban ssl.SSLContext po sidru. A budući da će i taj certifikat na kraju isteći ili ga CA rotirati, tretirajte ga kao bilo koji drugi certifikat na uređaju.