9.17. Šifrirani socketi i TLS

Sve što je do sada obrađeno prenosi bajtove u otvorenom obliku. Bilo koji uređaj na putu između kamere i poslužitelja – kućni usmjerivač, davatelj internetskih usluga, zlonamjerna pristupna točka u kafiću – u načelu može čitati ili mijenjati ono što prolazi kroz njega. Za većinu internetskog prometa to nije prihvatljivo. Standardno rješenje je omotati vezu u sloj šifriranja: TLS, protokol Transport Layer Security. Ikona lokota „HTTPS” u pregledniku je TLS koji radi preko TCP-a, i isto to omatanje je ono što čini bilo koji drugi internetski protokol „sigurnim”. Modul ssl na kameri je ono što omata socket u TLS.

9.17.1. Što TLS dodaje i s čime kamera dolazi

TLS se nalazi između TCP-a i aplikacije – aplikacija zapisuje bajtove u socket omotan TLS-om, TLS ih šifrira i predaje rezultat TCP-u, a postupak se obrće na drugoj strani. U svom punom obliku TLS daje tri jamstva povrh običnog TCP-a:

  • Povjerljivost. Prisluškivači na putu ne mogu čitati ono što dvije krajnje točke razmjenjuju.

  • Integritet. Svaka izmjena prometa tijekom prijenosa se otkriva; veza se prekida umjesto da isporuči izmijenjene podatke.

  • Autentifikacija. Poslužitelj dokazuje da je upravo imenovani poslužitelj, a ne uljez (i, opcionalno, klijent također dokazuje tko je on).

Prva dva proizlaze iz samog šifriranja. Treće zahtijeva certifikate na barem jednoj strani, plus nešto unaprijed pouzdano na temelju čega se ti certifikati provjeravaju. OpenMV kamera dolazi bez ikakvog ugrađenog spremišta certifikata: svježe flešana kamera ne vjeruje nijednom certifikacijskom tijelu, nema vlastiti poslužiteljski certifikat, a zadani način provjere (ssl.CERT_NONE) ne provjerava certifikat druge strane ni prema čemu. Dakle, izvan kutije, TLS na kameri daje vam prva dva jamstva – šifriranje protiv prisluškivanja i neovlaštene izmjene od strane pasivnog promatrača – ali ne i treće.

9.17.2. Šifriranje odlazne veze

Najjednostavnija upotreba je omatanje odlazne TCP veze. Tok je: otvorite normalni TCP socket, predajte ga funkciji ssl.wrap_socket(), zatim čitajte i pišite kroz omotani socket točno onako kako biste to činili s običnim:

import socket
import ssl

addr = socket.getaddrinfo("example.com", 443)[0][-1]
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(addr)

s = ssl.wrap_socket(sock)

s.send(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
print(s.recv(4096))
s.close()

Omatanje izvodi TLS rukovanje (handshake); nakon toga svaki bajt kroz s.send se šifrira na izlazu, a svaki bajt iz s.recv bio je šifriran na žici. Nikakvi certifikati nisu konfigurirani, nikakvo sidro povjerenja nije navedeno – TLS jednostavno dogovara efemerni ključ sesije s bilo kojim poslužiteljem koji se javi i koristi ga.

Dijagram s dva stupca označena s "client" i "server". Iscrtkana vodoravna linija blizu vrha označena je s "TCP connection already open". Ispod nje tri strelice prikazuju TLS rukovanje: "ClientHello" od klijenta prema poslužitelju, "ServerHello + certificate + key share" natrag, i ponovno "Finished" prema naprijed. Druga iscrtkana vodoravna linija ispod označena je s "TLS session open -- everything after this is encrypted". Dvije debele dvosmjerne strelice ispod nje nose "encrypted data".

TLS rukovanje koje izvodi ssl.wrap_socket(). Nalazi se povrh već otvorene TCP veze iz prethodne slike; čim obje strane pošalju Finished, ostatak razgovora je šifriran u oba smjera.

Upozorenje

Ovo je samo šifriranje, ne i autentificirani TLS. Kamera sigurno razgovara s bilo čime što se javilo na drugom kraju TCP veze. Ako čovjek u sredini (man-in-the-middle) preusmjeri vezu na poslužitelj koji kontrolira i taj poslužitelj predstavi bilo koji certifikat, rukovanje i dalje uspijeva i kamera završava sigurno razgovarajući s napadačem. Koristite ovaj način samo kada čovjek u sredini nije dio modela prijetnje – zatvorena lokalna mreža, razvojno okruženje, kamera koja razgovara s uslugom koja radi na istom hardveru – a ne kada se obraćate javnom internetu.

Za stvarnu autentifikaciju – kamera koja provjerava javni poslužitelj, kamera koja djeluje kao TLS poslužitelj, ili uzajamni TLS – morate dovesti certifikate na uređaj. Cijela priča nalazi se u Rad s TLS certifikatima.

Isto omatanje radi za dolazni TCP promet, odabirom poslužiteljskog protokola i prosljeđivanjem server_side=True funkciji ssl.wrap_socket(). Gornje upozorenje i dalje vrijedi: bez vlastitog certifikata kamera ne može dokazati klijentu tko je, a znatiželjni klijent bi na većini TLS stogova vidio neuspjeh rukovanja zbog „no certificate”. Produkcijski tijek rada s certifikatima je ono što omogućuje korisno pokretanje kamere kao TLS poslužitelja.

9.17.3. Uz asyncio

Poglavlje o asyncio prikazalo je asyncio.open_connection() za obične TCP klijente. Isti poziv prihvaća ključnu riječ ssl=True koja omata vezu u TLS, opet bez ikakve postavke certifikata:

import asyncio

async def main():
    reader, writer = await asyncio.open_connection(
        "example.com", 443, ssl=True,
    )
    writer.write(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
    await writer.drain()
    print(await reader.read(4096))
    writer.close()
    await writer.wait_closed()

asyncio.run(main())

Par reader/writer iza TLS veze ima isti oblik kao i za običnu TCP vezu – razlikuje se samo postavljanje. Vrijedi isto upozorenje o autentifikaciji: sam ssl=True daje samo šifriranje, a ne i provjeru.

9.17.4. DTLS – TLS preko UDP-a

TLS o kojem se do sada raspravljalo putuje povrh TCP-a. Paralelni protokol za UDP je DTLS (Datagram TLS), a modul ssl na kameri ga podržava na isti način. Tamo gdje TLS pretvara jednu TCP vezu u jedan šifrirani tok bajtova, DTLS pretvara jedan UDP socket u tok šifriranih, pojedinačno isporučenih datagrama – tako da se svojstva gubitka / izvanrednog redoslijeda / nepostojanja kontrole protoka UDP-a iz UDP – pošalji paket, nadaj se najboljem sva prenose, pri čemu su bajtovi unutar svakog datagrama sada šifrirani.

Omatanje izgleda isto kao u slučaju TLS-a, samo s SOCK_DGRAM socketom i DTLS protokolnim konstantama:

import socket
import ssl

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(socket.getaddrinfo("example.com", 4433)[0][-1])

ctx = ssl.SSLContext(ssl.PROTOCOL_DTLS_CLIENT)
s = ctx.wrap_socket(sock)

s.send(b"ping")
print(s.recv(64))
s.close()

(Pozivanje connect() na UDP socketu ne otvara vezu – samo pamti zadano odredište kako sljedeći pozivi send() / recv() to ne bi morali ponavljati. DTLS-u treba to fiksno odredište kako bi protiv njega izveo svoje rukovanje.)

Rukovanje ima isti oblik kao TLS dijagram iznad; razlika je u tome što je svaka poruka rukovanja sama po sebi UDP datagram, i svaka strana će ponoviti pokušaj pri gubitku.

Napomena

Prekida li gubitak paketa šifriranje? Ne. Svaki DTLS paket nosi sekvencijski broj, a šifriranje koristi taj broj za proizvodnju različitog izlaza za svaki paket – pa se isti ulaz nikada ne šifrira u iste bajtove dvaput, i svaki paket može biti dešifriran sam za sebe a da prethodni nije ni stigao. Izgubljeni paketi ili oni izvan redoslijeda ne razsinkroniziraju dvije strane. (Samo rukovanje je jedini dio koji mora pouzdano stići, a DTLS to rješava vlastitom ponovnom predajom.)

Vrijedi isto upozorenje o samo šifriranju bez certifikata odozgo: DTLS rukovanje protiv CERT_NONE druge strane šifrira promet, ali ne provjerava tko je druga strana. Cijeli DTLS tijek rada – certifikati, poslužiteljski kolačić protiv lažiranja, kako je ovo ista površina kao TLS osim protokolnih konstanti – obrađen je uz TLS materijal u Rad s TLS certifikatima.

Asyncio verzija koristi isti neblokirajući UDP obrazac iz Utičnice s asyncio. Izvedite rukovanje sinkrono unaprijed, prebacite socket u neblokirajući način rada, zatim ispitujte unutar korutine:

import asyncio
import socket
import ssl

async def dtls_ping(target_addr, period_ms):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.connect(target_addr)

    # Handshake while still blocking, then switch to async polling.
    ctx = ssl.SSLContext(ssl.PROTOCOL_DTLS_CLIENT)
    s = ctx.wrap_socket(sock)
    s.setblocking(False)

    while True:
        try:
            s.send(b"ping")
        except OSError:
            pass
        await asyncio.sleep_ms(period_ms)

Rukovanje je jedino mjesto na kojem ova korutina blokira petlju događaja; nakon toga, svaki s.send / s.recv vraća se odmah (ili izaziva OSError), a await asyncio.sleep_ms održava ostatak programa u radu.

9.17.5. Dalje od ovoga

Sve više od TLS-a koji samo šifrira – provjera certifikata javnog HTTPS poslužitelja, pokretanje kamere kao autentificiranog TLS poslužitelja, uzajamni TLS između kamere i pozadinskog sustava, odabir ključeva i tipova ključeva, postupanje s istekom certifikata – nalazi se u Rad s TLS certifikatima. Taj odjeljak pokriva kako generirati samopotpisane certifikate za lokalno testiranje, kako dobiti certifikate potpisane od strane CA za produkciju, kako ih dovesti na kameru u ispravnom formatu (DER), kako provjeriti javni poslužitelj kada je kamera klijent, kako razmišljati o zaštiti ključeva na uređaju koji napadač može rastaviti, te kako planirati za dan kada certifikat istekne.

Za potpunu referencu ssl API-ja – podržane TLS verzije, skupove šifri i opcije konteksta – pogledajte ssl — SSL/TLS modul.