9.17. Titkosított socketek és TLS¶
Minden, amit eddig tárgyaltunk, titkosítatlanul mozgatja a bájtokat. A kamera és a kiszolgáló közötti útvonalon lévő bármely eszköz – az otthoni router, az internetszolgáltató, egy kávézó rosszindulatú hozzáférési pontja – elvileg elolvashatja vagy módosíthatja az áthaladó adatokat. Az internetes forgalom nagy részénél ez nem elfogadható. A szokásos megoldás az, hogy a kapcsolatot egy titkosítási réteggel burkoljuk be: ez a TLS, vagyis a Transport Layer Security protokoll. A böngészőben látható „HTTPS” lakat ikon a TCP felett futó TLS, és ugyanez a beburkolás teszi „biztonságossá” bármely más internetes protokollt is. A kamera ssl modulja az, ami egy socket objektumot TLS-be burkol.
9.17.1. Mit ad hozzá a TLS, és mivel érkezik a kamera¶
A TLS a TCP és az alkalmazás között helyezkedik el – az alkalmazás bájtokat ír egy TLS-be burkolt socketbe, a TLS titkosítja azokat, és az eredményt átadja a TCP-nek, a túloldalon pedig fordított a folyamat. Teljes formájában a TLS három garanciát ad a sima TCP-hez képest:
Bizalmasság. Az útvonalon lehallgatók nem tudják elolvasni, mit cserél a két végpont egymással.
Integritás. A forgalom bármilyen átvitel közbeni módosítása észlelésre kerül; a kapcsolat inkább megszakad, mintsem manipulált adatot szolgáltatna.
Hitelesítés. A kiszolgáló bizonyítja, hogy ő a megnevezett kiszolgáló, nem pedig egy szélhámos (és opcionálisan az ügyfél is bizonyíthatja, hogy ő kicsoda).
Az első kettő magából a titkosításból fakad. A harmadikhoz legalább az egyik oldalon tanúsítványok kellenek, valamint valami előre megbízhatónak tekintett dolog, amihez képest ezeket a tanúsítványokat ellenőrizni lehet. Az OpenMV kamera egyáltalán nem rendelkezik beépített tanúsítványtárolóval: egy frissen flashelt kamera egyetlen tanúsítványkibocsátóban sem bízik meg, nincs saját kiszolgálótanúsítványa, és az alapértelmezett ellenőrzési mód (ssl.CERT_NONE) nem ellenőrzi a partner tanúsítványát semmihez képest. Így alapból a kamerán a TLS az első két garanciát adja meg – titkosítást a passzív megfigyelő általi lehallgatás és manipuláció ellen –, de a harmadikat nem.
9.17.2. Kimenő kapcsolat titkosítása¶
A legegyszerűbb felhasználás egy kimenő TCP-kapcsolat beburkolása. A folyamat: nyiss egy normál TCP socketet, add át a ssl.wrap_socket() függvénynek, majd olvass és írj a beburkolt socketen keresztül pontosan ugyanúgy, ahogyan a sima socketen tennéd:
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()
A beburkolás végrehajtja a TLS-kézfogást; ezt követően minden bájt, amely a s.send hívásán keresztül halad, kifelé menet titkosítva lesz, és a s.recv hívásból érkező minden bájt titkosítva volt a vonalon. Nem konfiguráltunk tanúsítványokat, nem adtunk meg megbízhatósági horgonyt – a TLS egyszerűen egyeztet egy ideiglenes munkamenetkulcsot azzal a kiszolgálóval, amelyik válaszol, és azt használja.
A TLS-kézfogás, amelyet a ssl.wrap_socket() futtat. Az előző ábra már nyitott TCP-kapcsolatán ül; miután mindkét oldal elküldte a Finished üzenetet, a beszélgetés többi része mindkét irányban titkosítva van.¶
Figyelem
Ez csak titkosítás, nem hitelesített TLS. A kamera biztonságosan beszél azzal, ami a TCP-kapcsolat másik végén válaszolt. Ha egy közbeékelődő támadó (man-in-the-middle) átirányítja a kapcsolatot egy általa vezérelt kiszolgálóra, és az a kiszolgáló bármilyen tanúsítványt mutat be, a kézfogás akkor is sikeres lesz, és a kamera végül biztonságosan a támadóval beszél. Ezt a módot csak akkor használd, ha a közbeékelődő támadó nem része a fenyegetési modellnek – egy zárt helyi hálózat, egy fejlesztői környezet, a kamera ugyanazon a hardveren futó szolgáltatással beszél –, nem pedig akkor, amikor a nyilvános internet felé nyúlsz ki.
A valódi hitelesítéshez – ahhoz, hogy a kamera ellenőrizzen egy nyilvános kiszolgálót, hogy a kamera TLS-kiszolgálóként működjön, vagy a kölcsönös TLS-hez – tanúsítványokat kell az eszközre juttatnod. A teljes történet itt található: TLS tanúsítványok kezelése.
Ugyanez a beburkolás működik a bejövő TCP-forgalomra is, a kiszolgálóprotokoll kiválasztásával és a server_side=True átadásával a ssl.wrap_socket() függvénynek. A fenti figyelmeztetés továbbra is érvényes: saját tanúsítvány nélkül a kamera nem tudja bizonyítani az ügyfél felé, hogy ő kicsoda, és egy kíváncsi ügyfél a legtöbb TLS-stacken „nincs tanúsítvány” kézfogási hibát látna. A gyártási oldali tanúsítvány-munkafolyamat az, ami lehetővé teszi a kamera TLS-kiszolgálóként való hasznos futtatását.
9.17.3. Asyncio használatával¶
Az asyncio fejezet bemutatta a asyncio.open_connection() függvényt a sima TCP-ügyfelekhez. Ugyanaz a hívás elfogad egy ssl=True kulcsszót, amely TLS-be burkolja a kapcsolatot, szintén bármilyen tanúsítvány-beállítás nélkül:
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())
A TLS-kapcsolat mögötti olvasó/író pár ugyanolyan alakú, mint egy sima TCP-kapcsolat esetében – csak a beállítás különbözik. A hitelesítésre vonatkozó ugyanazon figyelmeztetés érvényes: az ssl=True önmagában csak titkosítást ad, nem ellenőrzést.
9.17.4. DTLS – TLS UDP felett¶
Az eddig tárgyalt TLS a TCP tetején fut. Az UDP párhuzamos protokollja a DTLS (Datagram TLS), és a kamera ssl modulja ugyanúgy támogatja. Míg a TLS egy TCP-kapcsolatot egyetlen titkosított bájtfolyammá alakít, addig a DTLS egy UDP socketet titkosított, egyenként kézbesített datagramok folyamává alakít – így az UDP veszteséges / sorrenden kívüli / nincs forgalomszabályozás tulajdonságai a UDP – küldj egy csomagot, remélj a legjobbat oldalról mind átöröklődnek, azzal a különbséggel, hogy az egyes datagramokon belüli bájtok most titkosítva vannak.
A beburkolás ugyanúgy néz ki, mint a TLS esetében, csak egy SOCK_DGRAM sockettel és a DTLS protokollkonstansokkal:
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()
(A connect() hívása egy UDP socketen nem nyit kapcsolatot – csak megjegyez egy alapértelmezett célt, hogy a későbbi send() / recv() hívásoknak ne kelljen azt megismételniük. A DTLS-nek szüksége van erre a rögzített célra, hogy lefuttassa ellene a kézfogását.)
A kézfogás ugyanolyan alakú, mint a fenti TLS-ábra; a különbség az, hogy minden kézfogási üzenet maga egy UDP datagram, és bármelyik oldal újrapróbálkozik veszteség esetén.
Megjegyzés
A csomagvesztés megtöri a titkosítást? Nem. Minden DTLS-csomag hordoz egy sorszámot, és a titkosítás ezt a számot használja, hogy minden csomaghoz eltérő kimenetet állítson elő – így ugyanaz a bemenet soha nem titkosítódik kétszer ugyanazokra a bájtokra, és bármely csomag önmagában dekódolható anélkül, hogy az előzőnek meg kellett volna érkeznie. Az elveszett vagy sorrenden kívüli csomagok nem szinkronizálják ki a két oldalt. (Maga a kézfogás az egyetlen rész, amelynek megbízhatóan kell megérkeznie, és ezt a DTLS a saját újraküldésével kezeli.)
A fentebbi, csak titkosítást nyújtó, tanúsítvány nélküli figyelmeztetés ugyanúgy érvényes: egy CERT_NONE partner elleni DTLS-kézfogás titkosítja a forgalmat, de nem ellenőrzi, hogy ki van a túloldalon. A teljes DTLS-munkafolyamat – tanúsítványok, a kiszolgálóoldali hamisítás elleni cookie, hogy ez hogyan ugyanaz a felület, mint a TLS, eltekintve a protokollkonstansoktól – a TLS-anyaggal együtt itt található: TLS tanúsítványok kezelése.
Az asyncio változat ugyanazt a nem blokkoló UDP-mintát használja a Socketek asyncióval oldalról. Végezd el a kézfogást szinkronban elöl, állítsd a socketet nem blokkoló módba, majd kérdezd le egy korutinon belül:
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)
A kézfogás az egyetlen hely, ahol ez a korutin blokkolja az eseményhurkot; ezután minden s.send / s.recv azonnal visszatér (vagy OSError kivételt vált ki), és az await asyncio.sleep_ms futásban tartja a program többi részét.
9.17.5. Tovább lépve¶
Minden, ami több, mint a csak titkosítást nyújtó TLS – egy nyilvános HTTPS-kiszolgáló tanúsítványának ellenőrzése, a kamera hitelesített TLS-kiszolgálóként való futtatása, kölcsönös TLS a kamera és egy háttérrendszer között, kulcsok és kulcstípusok kiválasztása, a tanúsítvány lejáratának kezelése – itt található: TLS tanúsítványok kezelése. Az a szakasz tárgyalja, hogyan generálhatsz önaláírt tanúsítványokat helyi teszteléshez, hogyan szerezhetsz CA által aláírt tanúsítványokat gyártáshoz, hogyan juttathatod azokat a kamerára a megfelelő formátumban (DER), hogyan ellenőrizhetsz egy nyilvános kiszolgálót, amikor a kamera az ügyfél, hogyan gondolkodj a kulcsvédelemről egy olyan eszközön, amelyet a támadó szétszedhet, és hogyan tervezz arra a napra, amikor a tanúsítvány lejár.
A teljes ssl API-referenciáért – a támogatott TLS-verziókért, titkosítási csomagokért és környezeti beállításokért – lásd: ssl — SSL/TLS modul.