14.4.5. Weryfikacja publicznego serwera (kamera jako klient)

Wszystko, co napisano na poprzedniej stronie o kliencie, który „już ma certyfikat główny”, jest prawdą w przypadku przeglądarek, telefonów i komputerów PC, ale nie dotyczy kamery. Moduł ssl w MicroPython nie zawiera wbudowanego magazynu zaufania: świeżo zaprogramowana kamera nie ufa żadnemu urzędowi CA, a ustawienie domyślne (ssl.CERT_NONE) niczego nie weryfikuje i jest całkowicie otwarte na atak typu man-in-the-middle. Dlatego gdy kamera jest klientem nawiązującym połączenie z publicznym serwerem TLS (API HTTPS, brokerem MQTT, …) i chcesz, aby naprawdę weryfikowała ten serwer, musisz samodzielnie dostarczyć kotwicę zaufania.

Mechanika jest taka sama jak w przykładzie z klientem używającym certyfikatu samopodpisanego na stronie Certyfikaty self-signed; jedyną różnicą jest to, że plik, który ładujesz, to prawdziwy certyfikat CA zamiast własnego certyfikatu partnera:

  1. Zdobądź certyfikat CA, który stanowi kotwicę łańcucha serwera. „Kotwica” oznacza certyfikat znajdujący się na (lub blisko) szczycie łańcucha serwera, który wybierasz jako punkt wyjścia swojego zaufania. Serwer TLS wysyła swój certyfikat końcowy (leaf) i zazwyczaj swoje certyfikaty pośrednie; nigdy nie wysyła swojego certyfikatu głównego. Musisz samodzielnie uzyskać tę kotwicę zaufania, i to niezależnie od serwera – proste zaufanie wszystkiemu, co przekaże serwer, całkowicie zniweczyłoby sens weryfikacji.

    Najpierw dowiedz się, który urząd CA faktycznie wystawił certyfikat serwera. Na przykład dla openmv.io

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

    Blok Certificate chain wymienia każdy certyfikat wraz z jego podmiotem (s:) i wystawcą (i:); nowsze wersje OpenSSL drukują także wiersze a: (typ klucza) i v: (okres ważności), które możesz tu zignorować:

    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
    

    Wpis 0 to certyfikat końcowy (openmv.io), wystawiony przez pośredni E8. Wpis 1 to ten pośredni, wystawiony przez certyfikat główny ISRG Root X1. Wystawca (i:) najwyższego wpisu wskazuje certyfikat główny – tutaj ISRG Root X1. (Pośrednim jest E8, a nie R10 / R11, które mogłeś widzieć gdzie indziej, ponieważ openmv.io używa certyfikatu ECDSA; Let’s Encrypt podpisuje certyfikaty końcowe ECDSA swoimi pośrednimi z serii E, a certyfikaty końcowe RSA tymi z serii R. Oba prowadzą do ISRG Root X1.)

    OpenSSL drukuje również wiersze depth= i może zgłosić certyfikat główny z Verification: OK. Dzieje się tak tylko dlatego, że twój komputer PC już ufa ISRG Root X1 – serwer go nie wysłał (serwer nigdy nie wysyła swojego certyfikatu głównego), a kamera, nie mając magazynu zaufania, również go nie będzie miała. Właśnie dlatego musisz go dostarczyć.

    Pobierz ten certyfikat główny z opublikowanych certyfikatów głównych samego urzędu CA. Let’s Encrypt udostępnia wszystkie swoje na stronie certyfikatów Let’s Encrypt; bezpośredni plik dla ISRG Root X1 to isrgrootx1.pem (oferują go także w gotowej postaci jako isrgrootx1.der). Inne urzędy CA publikują swoje na podobnej stronie „root certificates” / „repository”; kanonicznym publicznym zbiorem jest program Mozilla CA (CCADB). Potwierdź, że pobrałeś właściwy plik, porównując jego odcisk palca z wartością publikowaną przez urząd CA (dodaj -inform DER, jeśli pobrałeś plik .der):

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

    Jeśli wolisz nie śledzić certyfikatu głównego, możesz zamiast tego skopiować certyfikat pośredni prosto z wyniku -showcerts (drugi blok -----BEGIN CERTIFICATE-----), zaufać mu i zaakceptować fakt, że będziesz musiał go odświeżać za każdym razem, gdy urząd CA dokona rotacji certyfikatu pośredniego – znacznie częściej niż certyfikatu głównego (zobacz kompromis poniżej).

  2. Przekonwertuj go na DER, dokładnie tak jak wcześniej:

    openssl x509 -in isrgrootx1.pem -outform DER -out ca.der
    
  3. Skopiuj ca.der do kamery (system plików lub ROMFS) i załaduj go jako kotwicę zaufania:

    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 jest tutaj wymagane: napędza ono SNI i jest nazwą sprawdzaną względem pola subjectAltName certyfikatu serwera.

Wskazówka

Skrót dla typowego przypadku. Let’s Encrypt jest najszerzej używanym publicznym urzędem CA, a zarówno jego certyfikaty RSA, jak i ECDSA prowadzą obecnie do ISRG Root X1 (jak pokazuje powyższy przykład openmv.io). Jeśli serwery, z którymi rozmawia twoja kamera, używają Let’s Encrypt, możesz całkowicie pominąć inspekcję: po prostu umieść isrgrootx1.der na kamerze i wywołaj na nim load_verify_locations.

To nie sprawia, że TLS działa z każdą witryną. Serwer, którego certyfikat pochodzi od innego urzędu CA (DigiCert, Google Trust Services, Amazon, Sectigo, …), nadal nie przejdzie weryfikacji, a ponieważ kamera ufa jednemu certyfikatowi DER na każdy ssl.SSLContext, nie możesz dołączyć każdego certyfikatu głównego tak, jak robi to przeglądarka. W razie wątpliwości zidentyfikuj rzeczywisty urząd CA serwera, jak pokazano powyżej, i zaufaj temu certyfikatowi głównemu.

To, któremu certyfikatowi ufasz, jest kwestią kompromisu:

  • Certyfikat główny (zalecane). Długowieczny – często liczony w dziesięcioleciach – więc ca.der rzadko się zmienia. Wymaga, aby serwer wysłał swój certyfikat pośredni, żeby mbedTLS mógł zbudować ścieżkę leaf → pośredni → twój zaufany certyfikat główny; praktycznie każdy poprawnie skonfigurowany serwer publiczny to robi.

  • Certyfikat pośredni. Również działa i działa nadal, nawet jeśli serwer pominie certyfikat pośredni, ale certyfikaty pośrednie są poddawane rotacji znacznie częściej niż główne, więc będziesz musiał odświeżać ca.der częściej.

  • Sam certyfikat końcowy (przypinanie certyfikatu, czyli pinning). Najszczelniejszy, ale certyfikat końcowy zmienia się przy każdym odnowieniu – mniej więcej co 90 dni w przypadku Let’s Encrypt – więc ma to sens tylko wtedy, gdy kontrolujesz również serwer i możesz wypchnąć nowy pin do każdej kamery jednocześnie. Dokładnie to robi przykład z klientem używającym certyfikatu samopodpisanego.

Informacja

ssl.SSLContext.load_verify_locations() przyjmuje pojedynczy certyfikat CA zakodowany w DER, więc kamera ufa dokładnie jednej kotwicy naraz. Aby dotrzeć do serwerów objętych różnymi urzędami CA, użyj osobnego ssl.SSLContext dla każdej kotwicy. A ponieważ ten certyfikat sam w końcu wygaśnie lub zostanie poddany rotacji przez urząd CA, traktuj go jak każdy inny certyfikat na urządzeniu.