14.4.5. Julkisen palvelimen varmentaminen (kamera asiakkaana)

Kaikki edellisellä sivulla kerrottu siitä, että asiakkaalla on ”jo juurivarmenne”, pätee selaimiin, puhelimiin ja tietokoneisiin – mutta se ei päde kameraan. MicroPythonin ssl ei sisällä mitään valmista luottamusvarastoa: juuri päivitetty kamera ei luota mihinkään varmenneviranomaiseen (CA), ja oletusasetus (ssl.CERT_NONE) ei varmenna mitään ja on täysin altis välimieshyökkäykselle. Niinpä kun kamera on asiakas, joka muodostaa yhteyden ulospäin julkiseen TLS-palvelimeen (HTTPS-API, MQTT-välittäjä, …) ja haluat sen aidosti varmentavan kyseisen palvelimen, sinun on toimitettava luottamusankkuri itse.

Mekaniikka on sama kuin Itse allekirjoitetut varmenteet-sivun itse allekirjoitetun asiakkaan esimerkissä; ainoa ero on, että lataamasi tiedosto on aito CA-varmenne vastapuolen oman varmenteen sijaan:

  1. Hanki CA-varmenne, joka ankkuroi palvelimen ketjun. ”Ankkuroi” tarkoittaa ketjun ylimpänä (tai lähellä huippua) olevaa varmennetta, jonka valitset luottamuksen lähtökohdaksi. TLS-palvelin lähettää lehtivarmenteensa ja yleensä välivarmenteensa; se ei koskaan lähetä juurivarmennettaan. Sinun on hankittava tuo luottamusankkuri itse ja palvelimesta riippumatta – pelkkä luottaminen siihen, mitä palvelin sattuu antamaan, tekisi koko varmentamisen tarkoituksettomaksi.

    Selvitä ensin, mikä CA tosiasiassa myönsi palvelimen varmenteen. Esimerkiksi osoitteelle openmv.io

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

    Certificate chain -lohko luettelee kunkin varmenteen sen kohteen (s:) ja myöntäjän (i:) kanssa; uudempi OpenSSL tulostaa myös a: (avaintyyppi)- ja v: (voimassaolo) -rivit, jotka voit jättää tässä huomiotta:

    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
    

    Merkintä 0 on lehtivarmenne (openmv.io), jonka on myöntänyt välivarmenne E8. Merkintä 1 on tuo välivarmenne, jonka on myöntänyt juurivarmenne ISRG Root X1. Ylimmän merkinnän myöntäjä (i:) nimeää juurivarmenteen – tässä ISRG Root X1. (Välivarmenne on E8 eikä muualla mahdollisesti näkemäsi R10 / R11, koska openmv.io käyttää ECDSA-varmennetta; Let’s Encrypt allekirjoittaa ECDSA-lehtivarmenteet E-sarjan välivarmenteillaan ja RSA-lehtivarmenteet R-sarjan välivarmenteillaan. Molemmat ketjuuntuvat juurivarmenteeseen ISRG Root X1.)

    OpenSSL tulostaa myös depth= -rivejä ja saattaa raportoida juurivarmenteen tilalla Verification: OK. Näin käy vain siksi, että oma tietokoneesi luottaa jo varmenteeseen ISRG Root X1 – palvelin ei lähettänyt sitä (palvelin ei koskaan lähetä juurivarmennettaan), eikä kamerallakaan, jolla ei ole luottamusvarastoa, ole sitä. Juuri siksi sinun on toimitettava se.

    Lataa tuo juurivarmenne CA:n omista julkaisemista juurivarmenteista. Let’s Encrypt luetteloi kaikki omansa Let’s Encrypt certificates -sivulla; suora tiedosto ISRG Root X1:lle on isrgrootx1.pem (he tarjoavat sen myös valmiiksi koodattuna muodossa isrgrootx1.der). Muut CA:t julkaisevat omansa vastaavalla ”root certificates” / ”repository” -sivulla; kanoninen julkinen joukko on Mozilla CA -ohjelma (CCADB). Varmista, että haet oikean tiedoston, vertaamalla sen sormenjälkeä CA:n julkaisemaan arvoon (lisää -inform DER, jos latasit .der-tiedoston):

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

    Jos et halua seurata juurivarmennetta, voit sen sijaan kopioida välivarmenteen suoraan -showcerts-tulosteesta (toinen -----BEGIN CERTIFICATE----- -lohko), luottaa siihen ja hyväksyä, että sinun on päivitettävä se aina, kun CA vaihtaa välivarmenteen – huomattavasti useammin kuin juurivarmenteen (katso kompromissit alla).

  2. Muunna se DER-muotoon, aivan kuten aiemmin:

    openssl x509 -in isrgrootx1.pem -outform DER -out ca.der
    
  3. Kopioi ca.der kameraan (tiedostojärjestelmään tai ROMFS:ään) ja lataa se luottamusankkuriksi:

    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 vaaditaan tässä: se ohjaa SNI:tä ja on nimi, joka tarkistetaan palvelimen varmenteen subjectAltName-kentästä.

Vihje

Yleisimmän tapauksen oikotie. Let’s Encrypt on laajimmin käytetty julkinen CA, ja sekä sen RSA- että ECDSA-varmenteet ketjuuntuvat tällä hetkellä juurivarmenteeseen ISRG Root X1 (kuten yllä oleva openmv.io-esimerkki osoittaa). Jos palvelimet, joiden kanssa kamera kommunikoi, käyttävät Let’s Encryptiä, voit ohittaa tarkastelun kokonaan: laita vain isrgrootx1.der kameraan ja kutsu sille load_verify_locations.

Tämä ei saa TLS:ää toimimaan kaikkien sivustojen kanssa. Palvelin, jonka varmenne tulee eri CA:lta (DigiCert, Google Trust Services, Amazon, Sectigo, …), epäonnistuu edelleen varmentamisessa, ja koska kamera luottaa yhteen DER-varmenteeseen ssl.SSLContext -kohtaisesti, et voi niputtaa jokaista juurivarmennetta kuten selain. Epäselvissä tilanteissa tunnista palvelimen todellinen CA yllä kuvatulla tavalla ja luota siihen juurivarmenteeseen.

Se, mihin varmenteeseen luotat, on kompromissi:

  • Juurivarmenne (suositeltu). Pitkäikäinen – usein vuosikymmeniä – joten ca.der muuttuu harvoin. Se edellyttää, että palvelin lähettää välivarmenteensa, jotta mbedTLS voi rakentaa polun lehtivarmenne → välivarmenne → luotettu juurivarmenteesi; käytännössä jokainen oikein määritetty julkinen palvelin tekee näin.

  • Välivarmenne. Toimii myös ja jatkaa toimintaansa, vaikka palvelin jättäisi välivarmenteen pois, mutta välivarmenteet vaihdetaan paljon useammin kuin juurivarmenteet, joten joudut päivittämään ca.der-tiedoston useammin.

  • Itse lehtivarmenne (varmenteen kiinnitys, pinning). Tiukin, mutta lehtivarmenne muuttuu jokaisessa uusinnassa – noin 90 päivän välein Let’s Encryptillä – joten tällä on järkeä vain silloin, kun hallitset myös palvelinta ja voit työntää uuden kiinnityksen jokaiseen kameraan samanaikaisesti. Tätä juuri itse allekirjoitetun asiakkaan esimerkki tekee.

Muista

ssl.SSLContext.load_verify_locations() ottaa yhden DER-koodatun CA-varmenteen, joten kamera luottaa täsmälleen yhteen ankkuriin kerrallaan. Tavoittaaksesi palvelimia eri CA:iden alaisuudessa käytä erillistä ssl.SSLContext -oliota kullekin ankkurille. Ja koska kyseinen varmenne itsekin lopulta vanhenee tai CA vaihtaa sen, kohtele sitä kuten mitä tahansa muutakin laitteen varmennetta.