9.17. Salatut soketit ja TLS

Kaikki tähän asti käsitelty siirtää tavuja selkokielisenä. Mikä tahansa laite kameran ja palvelimen välisellä reitillä – kotireititin, internetpalveluntarjoaja tai haitallinen tukiasema kahvilassa – voi periaatteessa lukea tai muokata sen läpi kulkevaa liikennettä. Suurimmalle osalle internetliikennettä tämä ei ole hyväksyttävää. Vakioratkaisu on kääriä yhteys salauskerrokseen: TLS, eli Transport Layer Security -protokolla. Selaimen ”HTTPS”-lukkokuvake on TLS, joka toimii TCP:n päällä, ja sama käärintä tekee mistä tahansa muusta internetprotokollasta ”turvallisen”. Kameran ssl-moduuli on se, joka kääräisee socket-olion TLS:ään.

9.17.1. Mitä TLS lisää ja mitä kamerassa on mukana

TLS sijaitsee TCP:n ja sovelluksen välissä – sovellus kirjoittaa tavuja TLS:ään kääräistyyn sokettiin, TLS salaa ne ja luovuttaa tuloksen TCP:lle, ja prosessi käännetään toisinpäin toisessa päässä. Täydessä muodossaan TLS antaa kolme takuuta tavallisen TCP:n päälle:

  • Luottamuksellisuus. Reitillä olevat salakuuntelijat eivät pysty lukemaan sitä, mitä kaksi päätepistettä vaihtavat keskenään.

  • Eheys. Kaikki liikenteen muokkaus matkan aikana havaitaan; yhteys katkeaa sen sijaan, että toimittaisi peukaloitua dataa.

  • Todennus. Palvelin todistaa olevansa nimetty palvelin eikä huijari (ja valinnaisesti myös asiakas todistaa, kuka se on).

Kaksi ensimmäistä tulevat itse salauksesta. Kolmas tarvitsee varmenteita ainakin toisella puolella sekä jonkin etukäteen luotetun tahon, jota vastaan nämä varmenteet voidaan tarkistaa. OpenMV-kamerassa ei ole lainkaan sisäänrakennettua varmennevarastoa: vasta flashattu kamera ei luota mihinkään varmenneviranomaiseen, sillä ei ole omaa palvelinvarmennetta, eikä oletustarkistustila (ssl.CERT_NONE) tarkista vastapuolen varmennetta mitään vastaan. Joten heti pakkauksesta otettuna TLS kamerassa antaa kaksi ensimmäistä takuuta – salauksen passiivisen tarkkailijan salakuuntelua ja peukalointia vastaan – mutta ei kolmatta.

9.17.2. Lähtevän yhteyden salaaminen

Yksinkertaisin käyttötapa on lähtevän TCP-yhteyden kääriminen. Kulku on: avaa tavallinen TCP-soketti, luovuta se funktiolle ssl.wrap_socket(), ja lue ja kirjoita sitten kääräistyn soketin kautta tarkalleen samalla tavalla kuin tekisit tavallisen kanssa:

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()

Käärintä suorittaa TLS-kättelyn; sen jälkeen jokainen tavu, joka kulkee s.send-kutsun läpi, salataan ulosmenomatkalla, ja jokainen tavu s.recv-kutsusta oli salattu johdolla. Mitään varmenteita ei määritetty, mitään luottamusankkuria ei toimitettu – TLS vain neuvottelee lyhytaikaisen istuntoavaimen sen palvelimen kanssa, joka vastaa, ja käyttää sitä.

Kaavio, jossa on kaksi saraketta nimeltä "client" ja "server". Katkoviivainen vaakaviiva lähellä yläosaa on nimetty "TCP connection already open". Sen alla kolme nuolta näyttää TLS- kättelyn: "ClientHello" asiakkaalta palvelimelle, "ServerHello + certificate + key share" takaisin, ja "Finished" taas eteenpäin. Alla oleva toinen katkoviivainen vaakaviiva on nimetty "TLS session open -- everything after this is encrypted". Sen alla kaksi paksua kaksisuuntaista nuolta kuljettaa "encrypted data".

TLS-kättely, jonka ssl.wrap_socket() suorittaa. Se sijaitsee edellisen kuvan jo avoimen TCP-yhteyden päällä; kun molemmat puolet ovat lähettäneet Finished-viestin, loput keskustelusta on salattua molempiin suuntiin.

Varoitus

Tämä on pelkkää salausta, ei todennettua TLS:ää. Kamera puhuu turvallisesti minkä tahansa kanssa, joka vastasi TCP-yhteyden toisessa päässä. Jos välistävetäjä ohjaa yhteyden uudelleen hallitsemalleen palvelimelle ja kyseinen palvelin esittää minkä tahansa varmenteen, kättely onnistuu silti ja kamera päätyy puhumaan turvallisesti hyökkääjän kanssa. Käytä tätä tilaa vain silloin, kun välistävetäjä ei kuulu uhkamalliin – suljettu paikallisverkko, kehitysympäristö, kamera puhumassa samalla laitteistolla toimivan palvelun kanssa – ei silloin, kun otat yhteyttä julkiseen internetiin.

Todellista todennusta varten – kamera tarkistaa julkisen palvelimen, kamera toimii TLS-palvelimena tai keskinäinen TLS – sinun on tuotava varmenteet laitteeseen. Koko tarina on kohdassa TLS-varmenteiden kanssa työskentely.

Sama käärintä toimii saapuvalle TCP-liikenteelle valitsemalla palvelinprotokolla ja antamalla server_side=True funktiolle ssl.wrap_socket(). Yllä oleva varoitus pätee edelleen: ilman omaa varmennetta kamera ei voi todistaa asiakkaalle, kuka se on, ja utelias asiakas näkisi useimmissa TLS-pinoissa ”ei varmennetta” -kättelyvirheen. Tuotantopuolen varmennetyönkulku on se, mikä avaa kameran käytön TLS-palvelimena hyödyllisellä tavalla.

9.17.3. asyncion kanssa

asyncio-luku esitteli funktion asyncio.open_connection() tavallisille TCP-asiakkaille. Sama kutsu hyväksyy ssl=True-avainsanan, joka kääräisee yhteyden TLS:ään, jälleen ilman mitään varmennemääritystä:

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())

Lukija/kirjoittaja-pari TLS-yhteyden takana on samanmuotoinen kuin tavallisen TCP-yhteyden – vain asetukset eroavat. Sama todennusta koskeva varoitus pätee: pelkkä ssl=True antaa vain salauksen, ei todennusta.

9.17.4. DTLS – TLS UDP:n päällä

Tähän asti käsitelty TLS toimii TCP:n päällä. Vastaava protokolla UDP:lle on DTLS (Datagram TLS), ja kameran ssl-moduuli tukee sitä samalla tavalla. Siinä missä TLS muuttaa yhden TCP-yhteyden yhdeksi salatuksi tavuvirraksi, DTLS muuttaa yhden UDP-soketin salattujen, yksittäin toimitettujen datagrammien virraksi – joten UDP:n häviö-/järjestyksen-vaihtumis-/ei-vuonohjaus-ominaisuudet kohdasta UDP – lähetä paketti ja toivo parasta siirtyvät kaikki mukaan, kun kunkin datagrammin sisällä olevat tavut ovat nyt salattuja.

Käärintä näyttää samalta kuin TLS-tapauksessa, vain SOCK_DGRAM-soketin ja DTLS-protokollavakioiden kanssa:

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()

(Metodin connect() kutsuminen UDP-soketissa ei avaa yhteyttä – se vain muistaa oletuskohteen, jotta myöhempien send()- / recv()-kutsujen ei tarvitse toistaa sitä. DTLS tarvitsee kyseisen kiinteän kohteen, jota vastaan se suorittaa kättelynsä.)

Kättely on samanmuotoinen kuin yllä oleva TLS-kaavio; erona on se, että jokainen kättelyviesti on itse UDP-datagrammi, ja kumpi tahansa puoli yrittää uudelleen häviön sattuessa.

Muista

Rikkooko pakettien häviäminen salauksen? Ei. Jokainen DTLS-paketti kantaa järjestysnumeroa, ja salaus käyttää tätä numeroa tuottaakseen erilaisen tuloksen kullekin paketille – joten sama syöte ei koskaan salaudu samoiksi tavuiksi kahdesti, ja mikä tahansa paketti voidaan purkaa itsenäisesti ilman, että edellinen olisi saapunut. Hävinneet tai järjestyksestä poikkeavat paketit eivät vie kahta puolta epäsynkroniin. (Itse kättely on se yksi osa, jonka on saavuttava luotettavasti, ja DTLS hoitaa sen omalla uudelleenlähetyksellään.)

Sama yllä mainittu pelkkä-salaus-ilman-varmenteita-varoitus pätee: DTLS-kättely CERT_NONE-vastapuolta vastaan salaa liikenteen, mutta ei tarkista, kuka toinen puoli on. Koko DTLS-työnkulku – varmenteet, palvelinpuolen huijaamista estävä eväste, miten tämä on sama pinta kuin TLS protokollavakioita lukuun ottamatta – käsitellään TLS-materiaalin ohella kohdassa TLS-varmenteiden kanssa työskentely.

asyncio-versio käyttää samaa ei-estävän UDP:n mallia kohdasta Socketit asyncion kanssa. Suorita kättely synkronisesti etukäteen, vaihda soketti ei-estäväksi ja kysele sitten korutiinin sisällä:

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)

Kättely on tämän korutiinin ainoa paikka, joka estää tapahtumasilmukkaa; sen jälkeen jokainen s.send / s.recv palaa välittömästi (tai nostaa OSError-poikkeuksen), ja await asyncio.sleep_ms pitää muun ohjelman käynnissä.

9.17.5. Pidemmälle

Kaikki muu kuin pelkkä-salaus-TLS – julkisen HTTPS-palvelimen varmenteen tarkistaminen, kameran ajaminen todennettuna TLS-palvelimena, keskinäinen TLS kameran ja taustajärjestelmän välillä, avainten ja avaintyyppien valinta, varmenteen vanhenemisen käsittely – on kohdassa TLS-varmenteiden kanssa työskentely. Kyseinen osio käsittelee, miten luodaan itse allekirjoitettuja varmenteita paikallista testausta varten, miten hankitaan CA:n allekirjoittamia varmenteita tuotantoon, miten ne saadaan kameraan oikeassa muodossa (DER), miten julkinen palvelin tarkistetaan kun kamera on asiakas, miten ajatella avainten suojausta laitteessa jonka hyökkääjä saattaa purkaa osiin, ja miten varautua päivään, jolloin varmenne vanhenee.

Täydellinen ssl-API:n viite – tuetut TLS-versiot, salausalgoritmijoukot ja kontekstivaihtoehdot – katso ssl — SSL/TLS-moduuli.