9.17. Socket cifrati e TLS

Tutto quanto trattato finora trasferisce byte in chiaro. Qualsiasi dispositivo lungo il percorso tra la camera e il server – il router di casa, il provider di servizi internet, un punto di accesso malevolo in un bar – puo in linea di principio leggere o modificare cio che lo attraversa. Per la maggior parte del traffico internet questo non e accettabile. La soluzione standard consiste nell’avvolgere la connessione in uno strato di cifratura: TLS, il protocollo Transport Layer Security. L’icona del lucchetto «HTTPS» in un browser e TLS che gira su TCP, e lo stesso involucro e cio che rende «sicuro» qualsiasi altro protocollo internet. Il modulo ssl della camera e cio che avvolge un socket in TLS.

9.17.1. Cosa aggiunge TLS, e cosa offre la camera di serie

TLS si colloca tra TCP e l’applicazione – l’applicazione scrive byte su un socket avvolto in TLS, TLS li cifra e consegna il risultato a TCP, e il processo viene invertito dall’altro lato. Nella sua forma completa TLS fornisce tre garanzie in aggiunta al semplice TCP:

  • Riservatezza. Chi intercetta lungo il percorso non puo leggere cio che i due endpoint si scambiano.

  • Integrita. Qualsiasi modifica del traffico in transito viene rilevata; la connessione si interrompe anziche consegnare dati manomessi.

  • Autenticazione. Il server dimostra di essere il server nominato, non un impostore (e, facoltativamente, anche il client dimostra chi e).

Le prime due derivano dalla cifratura stessa. La terza richiede certificati su almeno un lato, oltre a qualcosa di gia fidato con cui verificare tali certificati. La camera OpenMV viene fornita senza alcun archivio di certificati integrato: una camera appena flashata non considera fidata alcuna autorita di certificazione, non possiede un proprio certificato del server, e la modalita di verifica predefinita (ssl.CERT_NONE) non controlla il certificato del peer rispetto ad alcunche. Quindi, di serie, TLS sulla camera offre le prime due garanzie – cifratura contro intercettazione e manomissione da parte di un osservatore passivo – ma non la terza.

9.17.2. Cifrare una connessione in uscita

L’uso piu semplice e avvolgere una connessione TCP in uscita. Il flusso e: aprire un normale socket TCP, passarlo a ssl.wrap_socket(), quindi leggere e scrivere attraverso il socket avvolto esattamente come faresti con quello in chiaro:

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

L’avvolgimento esegue l’handshake TLS; in seguito ogni byte che passa attraverso s.send viene cifrato in uscita e ogni byte proveniente da s.recv era cifrato sul filo. Non e stato configurato alcun certificato, non e stato fornito alcun ancoraggio di fiducia – TLS si limita a negoziare una chiave di sessione effimera con qualunque server risponda e la utilizza.

Un diagramma con due colonne etichettate "client" e "server". Una linea orizzontale tratteggiata vicino alla cima e etichettata "connessione TCP gia aperta". Sotto di essa, tre frecce mostrano l'handshake TLS: "ClientHello" dal client al server, "ServerHello + certificato + scambio di chiavi" all'indietro, e "Finished" di nuovo in avanti. Una seconda linea orizzontale tratteggiata sotto e etichettata "sessione TLS aperta -- tutto dopo questo punto e cifrato". Due spesse frecce bidirezionali sotto di essa trasportano "dati cifrati".

L’handshake TLS che ssl.wrap_socket() esegue. Si appoggia sulla connessione TCP gia aperta della figura precedente; una volta che entrambi i lati hanno inviato Finished, il resto della conversazione e cifrato in entrambe le direzioni.

Avvertimento

Questa e cifratura soltanto, non TLS autenticato. La camera comunica in modo sicuro con qualunque cosa abbia risposto all’altro capo della connessione TCP. Se un attaccante man-in-the-middle reindirizza la connessione a un server che controlla e quel server presenta un qualsiasi certificato, l’handshake ha comunque successo e la camera finisce per comunicare in modo sicuro con l’attaccante. Usa questa modalita solo quando un man-in-the-middle non fa parte del modello di minaccia – una rete locale chiusa, un ambiente di sviluppo, la camera che comunica con un servizio in esecuzione sullo stesso hardware – non quando ti colleghi all’internet pubblico.

Per un’autenticazione reale – la camera che verifica un server pubblico, la camera che agisce da server TLS, oppure TLS reciproco – devi portare i certificati sul dispositivo. La storia completa e in Lavorare con i certificati TLS.

Lo stesso avvolgimento funziona per il traffico TCP in entrata, selezionando il protocollo server e passando server_side=True a ssl.wrap_socket(). L’avvertimento di cui sopra resta valido: senza un certificato proprio la camera non puo dimostrare la propria identita al client, e un client curioso vedrebbe un fallimento dell’handshake per «nessun certificato» sulla maggior parte degli stack TLS. Il flusso di lavoro dei certificati lato produzione e cio che sblocca l’esecuzione della camera come server TLS in modo utile.

9.17.3. Con asyncio

Il capitolo su asyncio ha mostrato asyncio.open_connection() per i client TCP in chiaro. La stessa chiamata accetta un argomento ssl=True che avvolge la connessione in TLS, ancora una volta senza alcuna configurazione di certificati:

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

La coppia reader/writer dietro una connessione TLS ha la stessa forma di quella per una connessione TCP in chiaro – cambia solo la configurazione. Vale la stessa avvertenza sull’autenticazione: ssl=True da solo fornisce solo cifratura, non verifica.

9.17.4. DTLS – TLS su UDP

TLS come discusso finora viaggia sopra TCP. Il protocollo parallelo per UDP e DTLS (Datagram TLS), e il modulo ssl della camera lo supporta allo stesso modo. Dove TLS trasforma una connessione TCP in un flusso di byte cifrato, DTLS trasforma un socket UDP in un flusso di datagrammi cifrati e consegnati individualmente – quindi le proprieta di perdita / fuori ordine / nessun controllo di flusso di UDP descritte in UDP – invia un pacchetto e spera per il meglio si trasferiscono tutte, con i byte all’interno di ciascun datagramma ora cifrati.

L’avvolgimento appare uguale al caso TLS, solo con un socket SOCK_DGRAM e le costanti del protocollo DTLS:

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

(Chiamare connect() su un socket UDP non apre una connessione – si limita a ricordare una destinazione predefinita in modo che le successive chiamate send() / recv() non debbano ripeterla. DTLS ha bisogno di quella destinazione fissa per eseguire il proprio handshake.)

L’handshake ha la stessa forma del diagramma TLS sopra; la differenza e che ciascun messaggio dell’handshake e esso stesso un datagramma UDP, ed entrambi i lati ritenteranno in caso di perdita.

Nota

La perdita di pacchetti interrompe la cifratura? No. Ciascun pacchetto DTLS porta un numero di sequenza, e la cifratura usa quel numero per produrre un output diverso per ogni pacchetto – cosi lo stesso input non si cifra mai due volte negli stessi byte, e qualsiasi pacchetto puo essere decifrato per conto proprio senza che quello precedente sia arrivato. I pacchetti persi o fuori ordine non desincronizzano i due lati. (L’handshake stesso e l’unica parte che deve arrivare in modo affidabile, e DTLS se ne occupa con la propria ritrasmissione.)

Vale la stessa avvertenza di cui sopra su cifratura-soltanto-senza-certificati: un handshake DTLS contro un peer CERT_NONE cifra il traffico ma non verifica chi sia l’altro lato. Il flusso di lavoro DTLS completo – certificati, il cookie anti-spoofing lato server, come questa sia la stessa superficie di TLS a parte le costanti del protocollo – e trattato insieme al materiale TLS in Lavorare con i certificati TLS.

La versione asyncio usa lo stesso schema UDP non bloccante di Socket con asyncio. Esegui l’handshake in modo sincrono in anticipo, imposta il socket come non bloccante, quindi esegui il polling all’interno di una coroutine:

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)

L’handshake e l’unico punto in cui questa coroutine blocca l’event loop; dopodiche, ogni s.send / s.recv ritorna immediatamente (o solleva OSError), e l”await asyncio.sleep_ms mantiene in esecuzione il resto del programma.

9.17.5. Andare oltre

Tutto cio che va oltre il TLS di sola cifratura – verificare il certificato di un server HTTPS pubblico, eseguire la camera come server TLS autenticato, TLS reciproco tra la camera e un back-end, scegliere le chiavi e i tipi di chiave, gestire la scadenza dei certificati – e in Lavorare con i certificati TLS. Quella sezione spiega come generare certificati autofirmati per i test locali, come ottenere certificati firmati da una CA per la produzione, come trasferirli sulla camera nel formato corretto (DER), come verificare un server pubblico quando la camera e il client, come ragionare sulla protezione delle chiavi su un dispositivo che un attaccante potrebbe smontare, e come pianificare per il giorno in cui il certificato scadra.

Per il riferimento completo dell’API ssl – versioni TLS supportate, suite di cifratura e opzioni del contesto – vedi ssl — modulo SSL/TLS.