14.4.5. Ověřování veřejného serveru (kamera jako klient)

Vše, co bylo na předchozí stránce řečeno o klientovi, který „už má kořenový certifikát“, platí pro prohlížeče, telefony a počítače – pro kameru to neplatí. Modul ssl v MicroPythonu nepřichází s žádným vestavěným úložištěm důvěry: čerstvě naflashovaná kamera nedůvěřuje žádné CA a výchozí nastavení (ssl.CERT_NONE) neověřuje vůbec nic a je dokořán otevřené útoku typu man-in-the-middle. Takže když je kamera klientem, který se připojuje ven k veřejnému TLS serveru (HTTPS API, MQTT broker, …), a chcete, aby tento server skutečně ověřila, musíte kotvu důvěry dodat sami.

Mechanismus je stejný jako u příkladu se self-signed klientem na Vlastnoručně podepsané certifikáty; jediný rozdíl je v tom, že soubor, který načítáte, je skutečný certifikát CA namísto vlastního certifikátu protistrany:

  1. Získejte certifikát CA, který ukotvuje řetězec serveru. „Ukotvuje“ znamená certifikát na vrcholu (nebo blízko vrcholu) řetězce serveru, který si zvolíte jako svůj výchozí bod důvěry. TLS server posílá svůj leaf certifikát a obvykle své mezilehlé (intermediate) certifikáty; nikdy neposílá svůj kořenový certifikát. Tuto kotvu důvěry si musíte obstarat sami a nezávisle na serveru – prosté důvěřování čemukoli, co vám server předá, by zcela popřelo smysl ověřování.

    Nejprve zjistěte, která CA vlastně vystavila certifikát serveru. Například pro openmv.io

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

    Blok Certificate chain vypisuje každý certifikát s jeho subjektem (s:) a vydavatelem (i:); novější OpenSSL navíc vypisuje řádky a: (typ klíče) a v: (platnost), které zde můžete ignorovat:

    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
    

    Položka 0 je leaf certifikát (openmv.io), vystavený mezilehlým certifikátem E8. Položka 1 je tento mezilehlý certifikát, vystavený kořenovým certifikátem ISRG Root X1. Vydavatel (i:) nejvyšší položky pojmenovává kořenový certifikát – zde ISRG Root X1. (Mezilehlý certifikát je E8 namísto R10 / R11, který jste možná viděli jinde, protože openmv.io používá certifikát ECDSA; Let’s Encrypt podepisuje ECDSA leaf certifikáty svými mezilehlými certifikáty řady E a RSA leaf certifikáty svými mezilehlými certifikáty řady R. Oba se řetězí ke ISRG Root X1.)

    OpenSSL také vypisuje řádky depth= a může hlásit kořenový certifikát s Verification: OK. K tomu dochází pouze proto, že váš počítač již kořenovému certifikátu ISRG Root X1 důvěřuje – server jej neposlal (server nikdy neposílá svůj kořenový certifikát) a kamera, která nemá žádné úložiště důvěry, jej rovněž mít nebude. Přesně proto jej musíte dodat.

    Stáhněte tento kořenový certifikát z vlastních publikovaných kořenových certifikátů dané CA. Let’s Encrypt všechny své katalogizuje na stránce certifikátů Let’s Encrypt; přímý soubor pro ISRG Root X1 je isrgrootx1.pem (nabízejí jej také předem zakódovaný jako isrgrootx1.der). Ostatní CA publikují své na podobné stránce „root certificates“ / „repository“; kanonickou veřejnou sadou je program Mozilla CA (CCADB). Potvrďte, že jste stáhli správný soubor, porovnáním jeho otisku (fingerprint) s hodnotou, kterou CA publikuje (přidejte -inform DER, pokud jste stáhli soubor .der):

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

    Pokud byste raději nesledovali kořenový certifikát, můžete místo toho zkopírovat mezilehlý certifikát přímo z výstupu -showcerts (druhý blok -----BEGIN CERTIFICATE-----), důvěřovat jemu a smířit se s tím, že jej musíte obnovit pokaždé, když CA mezilehlý certifikát obmění – mnohem častěji než kořenový (viz kompromis níže).

  2. Převeďte jej do formátu DER, přesně jako dříve:

    openssl x509 -in isrgrootx1.pem -outform DER -out ca.der
    
  3. Zkopírujte ca.der do kamery (souborový systém nebo ROMFS) a načtěte jej jako kotvu důvěry:

    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 zde povinný: řídí SNI a je to jméno kontrolované oproti subjectAltName certifikátu serveru.

Tip

Zkratka pro běžný případ. Let’s Encrypt je nejrozšířenější veřejnou CA a jak její RSA, tak ECDSA certifikáty se v současnosti řetězí ke ISRG Root X1 (jak ukazuje příklad openmv.io výše). Pokud servery, se kterými vaše kamera komunikuje, používají Let’s Encrypt, můžete inspekci zcela přeskočit: stačí umístit isrgrootx1.der na kameru a zavolat na něm load_verify_locations.

Toto neumožní funkční TLS ke každému webu. Server, jehož certifikát pochází od jiné CA (DigiCert, Google Trust Services, Amazon, Sectigo, …), při ověřování stále selže, a protože kamera důvěřuje jedinému DER certifikátu na ssl.SSLContext, nemůžete sdružit každý kořenový certifikát tak, jak to dělá prohlížeč. Pokud máte pochybnosti, identifikujte skutečnou CA serveru způsobem uvedeným výše a důvěřujte jejímu kořenovému certifikátu.

To, kterému certifikátu důvěřujete, je kompromis:

  • Kořenový certifikát (doporučeno). Má dlouhou životnost – často desetiletí – takže ca.der se mění jen zřídka. Vyžaduje, aby server poslal svůj mezilehlý certifikát, aby mbedTLS mohl sestavit cestu leaf → mezilehlý → váš důvěryhodný kořenový certifikát; to dělá prakticky každý správně nakonfigurovaný veřejný server.

  • Mezilehlý certifikát. Funguje také a funguje i tehdy, když server mezilehlý certifikát vynechá, avšak mezilehlé certifikáty se obměňují mnohem častěji než kořenové, takže budete muset ca.der obnovovat častěji.

  • Samotný leaf certifikát (certificate pinning). Nejtěsnější, ale leaf certifikát se mění při každém obnovení – u Let’s Encrypt zhruba každých 90 dní – takže to dává smysl jen tehdy, když zároveň ovládáte server a můžete nový pin nahrát do každé kamery synchronně. Přesně to dělá příklad se self-signed klientem.

Poznámka

ssl.SSLContext.load_verify_locations() přijímá jediný certifikát CA zakódovaný ve formátu DER, takže kamera důvěřuje právě jedné kotvě najednou. K dosažení serverů pod různými CA použijte samostatný ssl.SSLContext pro každou kotvu. A protože sám tento certifikát nakonec vyprší nebo jej CA obmění, zacházejte s ním jako s jakýmkoli jiným certifikátem na zařízení.