9.17. Krypterade sockets och TLS¶
Allt som har beskrivits hittills skickar bytes i klartext. Vilken enhet som helst på vägen mellan kameran och servern – hemroutern, internetleverantören, en illvillig accesspunkt på ett kafé – kan i princip läsa eller modifiera det som passerar. För större delen av internettrafiken är det inte acceptabelt. Den vanliga lösningen är att kapsla in anslutningen i ett lager av kryptering: TLS, protokollet Transport Layer Security. ”HTTPS”-låsikonen i en webbläsare är TLS som körs över TCP, och samma inkapsling är det som gör vilket annat internetprotokoll som helst ”säkert”. Kamerans ssl-modul är det som kapslar in en socket i TLS.
9.17.1. Vad TLS lägger till, och vad kameran levereras med¶
TLS ligger mellan TCP och applikationen – applikationen skriver bytes till en TLS-inkapslad socket, TLS krypterar dem och lämnar resultatet till TCP, och processen körs i omvänd ordning på andra sidan. I sin fullständiga form ger TLS tre garantier utöver vanlig TCP:
Konfidentialitet. Avlyssnare på vägen kan inte läsa det som de två ändpunkterna utbyter.
Integritet. Varje modifiering av trafiken under transport upptäcks; anslutningen bryts i stället för att leverera manipulerade data.
Autentisering. Servern bevisar att den är den namngivna servern, inte en bedragare (och valfritt bevisar även klienten vem den är).
De första två kommer från själva krypteringen. Den tredje kräver certifikat på minst en sida, plus något i förväg betrott att verifiera dessa certifikat mot. OpenMV-kameran levereras helt utan inbyggt certifikatlager: en nyflashad kamera litar inte på någon certifikatutfärdare, har inget eget servercertifikat, och standardläget för verifiering (ssl.CERT_NONE) kontrollerar inte motpartens certifikat mot någonting. Så direkt ur lådan ger TLS på kameran dig de två första garantierna – kryptering mot avlyssning och manipulering av en passiv observatör – men inte den tredje.
9.17.2. Kryptera en utgående anslutning¶
Den enklaste användningen är att kapsla in en utgående TCP-anslutning. Flödet är: öppna en vanlig TCP-socket, lämna den till ssl.wrap_socket(), och läs sedan och skriv genom den inkapslade socketen precis som du skulle göra med den vanliga:
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()
Inkapslingen utför TLS-handskakningen; därefter krypteras varje byte genom s.send på vägen ut och varje byte från s.recv var krypterad på ledningen. Inga certifikat konfigurerades, ingen förtroendeankare tillhandahölls – TLS förhandlar bara fram en tillfällig sessionsnyckel med vilken server som än svarar och använder den.
TLS-handskakningen som ssl.wrap_socket() kör. Den ligger ovanpå den redan öppna TCP-anslutningen från föregående figur; när båda sidor har skickat Finished är resten av konversationen krypterad i båda riktningarna.¶
Varning
Detta är endast kryptering, inte autentiserad TLS. Kameran kommunicerar säkert med vad som helst som svarade i andra änden av TCP-anslutningen. Om en man-in-the-middle omdirigerar anslutningen till en server den kontrollerar och den servern presenterar vilket certifikat som helst, lyckas handskakningen ändå och kameran slutar med att säkert kommunicera med angriparen. Använd detta läge bara när en man-in-the-middle inte är en del av hotmodellen – ett slutet lokalt nätverk, en utvecklingsmiljö, kameran som kommunicerar med en tjänst som körs på samma hårdvara – inte när du når ut till det offentliga internet.
För riktig autentisering – kameran som verifierar en offentlig server, kameran som agerar TLS-server, eller ömsesidig TLS – behöver du föra in certifikat på enheten. Hela berättelsen finns i Arbeta med TLS-certifikat.
Samma inkapsling fungerar för inkommande TCP-trafik, genom att välja serverprotokollet och skicka server_side=True till ssl.wrap_socket(). Varningen ovan gäller fortfarande: utan ett eget certifikat kan kameran inte bevisa vem den är för klienten, och en nyfiken klient skulle se ett handskakningsfel av typen ”inget certifikat” på de flesta TLS-stackar. Certifikatarbetsflödet på produktionssidan är det som låser upp möjligheten att köra kameran som TLS-server på ett användbart sätt.
9.17.3. Med asyncio¶
asyncio-kapitlet visade asyncio.open_connection() för vanliga TCP-klienter. Samma anrop accepterar ett ssl=True-nyckelord som kapslar in anslutningen i TLS, återigen utan någon certifikatkonfiguration:
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())
Läsar-/skrivarparet bakom en TLS-anslutning har samma form som för en vanlig TCP-anslutning – endast konfigurationen skiljer sig. Samma förbehåll om autentisering gäller: enbart ssl=True ger endast kryptering, inte verifiering.
9.17.4. DTLS – TLS över UDP¶
TLS som diskuterats hittills rider ovanpå TCP. Det parallella protokollet för UDP är DTLS (Datagram TLS), och kamerans ssl-modul stöder det på samma sätt. Där TLS gör en TCP-anslutning till en krypterad byteström gör DTLS en UDP-socket till en ström av krypterade, individuellt levererade datagram – så egenskaperna med förlust / felaktig ordning / ingen flödeskontroll hos UDP från UDP – skicka ett paket, hoppas på det bästa förs alla över, med byten inuti varje datagram nu krypterade.
Inkapslingen ser likadan ut som i TLS-fallet, bara med en SOCK_DGRAM-socket och DTLS-protokollkonstanterna:
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()
(Att anropa connect() på en UDP-socket öppnar inte en anslutning – det kommer bara ihåg en standarddestination så att efterföljande send()- / recv()-anrop inte behöver upprepa den. DTLS behöver den fasta destinationen för att köra sin handskakning mot.)
Handskakningen har samma form som TLS-diagrammet ovan; skillnaden är att varje handskakningsmeddelande självt är ett UDP-datagram, och endera sidan försöker igen vid förlust.
Anteckning
Bryter förlorade paket krypteringen? Nej. Varje DTLS-paket bär ett sekvensnummer, och krypteringen använder det numret för att producera olika utdata för varje paket – så samma indata krypteras aldrig till samma bytes två gånger, och vilket paket som helst kan dekrypteras på egen hand utan att det föregående har anlänt. Förlorade eller felaktigt ordnade paket desynkroniserar inte de två sidorna. (Själva handskakningen är den enda del som måste anlända tillförlitligt, och DTLS hanterar det med sin egen omsändning.)
Samma varning om endast-kryptering-utan-certifikat från ovan gäller: en DTLS-handskakning mot en CERT_NONE-motpart krypterar trafiken men verifierar inte vem den andra sidan är. Hela DTLS-arbetsflödet – certifikat, anti-spoofing-cookien på serversidan, hur detta är samma yta som TLS bortsett från protokollkonstanterna – behandlas tillsammans med TLS-materialet i Arbeta med TLS-certifikat.
Asyncio-versionen använder samma icke-blockerande UDP-mönster från Sockets med asyncio. Gör handskakningen synkront i förväg, växla socketen till icke-blockerande, och polla sedan inuti en korutin:
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)
Handskakningen är det enda stället där denna korutin blockerar händelseloopen; därefter returnerar varje s.send / s.recv omedelbart (eller väcker OSError), och await asyncio.sleep_ms håller resten av programmet igång.
9.17.5. Gå vidare¶
Allt utöver endast-kryptering-TLS – att verifiera en offentlig HTTPS-servers certifikat, att köra kameran som en autentiserad TLS-server, ömsesidig TLS mellan kameran och en backend, att välja nycklar och nyckeltyper, att hantera certifikatutgång – finns i Arbeta med TLS-certifikat. Det avsnittet behandlar hur man genererar självsignerade certifikat för lokal testning, hur man erhåller CA-signerade certifikat för produktion, hur man får in dem på kameran i rätt format (DER), hur man verifierar en offentlig server när kameran är klienten, hur man tänker kring nyckelskydd på en enhet som en angripare kan ta isär, och hur man planerar för dagen då certifikatet löper ut.
För den fullständiga ssl-API-referensen – TLS-versioner som stöds, chiffersviter och kontextalternativ – se ssl — SSL/TLS-modul.