9.17. Sockets chiffrés et TLS

Tout ce qui a été abordé jusqu’ici fait transiter des octets en clair. N’importe quel appareil situé sur le chemin entre la caméra et le serveur – le routeur domestique, le fournisseur d’accès à Internet, un point d’accès malveillant dans un café – peut en principe lire ou modifier ce qui y transite. Pour la plupart du trafic Internet, cela n’est pas acceptable. La solution standard consiste à envelopper la connexion dans une couche de chiffrement : TLS, le protocole Transport Layer Security. L’icône de cadenas « HTTPS » d’un navigateur, c’est TLS fonctionnant par-dessus TCP, et c’est ce même enveloppement qui rend « sécurisé » tout autre protocole Internet. Le module ssl de la caméra est ce qui enveloppe un socket dans TLS.

9.17.1. Ce que TLS apporte, et ce qui est fourni avec la caméra

TLS s’intercale entre TCP et l’application – l’application écrit des octets dans un socket enveloppé par TLS, TLS les chiffre et transmet le résultat à TCP, et le processus est inversé de l’autre côté. Dans sa forme complète, TLS offre trois garanties qui s’ajoutent à TCP brut :

  • Confidentialité. Les espions présents sur le chemin ne peuvent pas lire ce que les deux extrémités échangent.

  • Intégrité. Toute modification du trafic en transit est détectée ; la connexion est rompue plutôt que de livrer des données altérées.

  • Authentification. Le serveur prouve qu’il est bien le serveur annoncé, et non un imposteur (et, en option, le client prouve lui aussi qui il est).

Les deux premières découlent du chiffrement lui-même. La troisième nécessite des certificats d’au moins un côté, ainsi qu’un élément préalablement approuvé permettant de vérifier ces certificats. La caméra OpenMV est fournie sans aucun magasin de certificats intégré : une caméra fraîchement flashée ne fait confiance à aucune autorité de certification, ne possède pas de certificat serveur propre, et le mode de vérification par défaut (ssl.CERT_NONE) ne contrôle le certificat du pair par rapport à rien. Ainsi, telle quelle, TLS sur la caméra vous offre les deux premières garanties – le chiffrement contre l’écoute clandestine et l’altération par un observateur passif – mais pas la troisième.

9.17.2. Chiffrer une connexion sortante

L’usage le plus simple consiste à envelopper une connexion TCP sortante. Le déroulement est le suivant : ouvrir un socket TCP normal, le passer à ssl.wrap_socket(), puis lire et écrire à travers le socket enveloppé exactement comme on le ferait avec le socket brut:

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’enveloppement effectue la poignée de main TLS ; ensuite, chaque octet passant par s.send est chiffré en sortie et chaque octet provenant de s.recv était chiffré sur le réseau. Aucun certificat n’a été configuré, aucune ancre de confiance n’a été fournie – TLS se contente de négocier une clé de session éphémère avec le serveur qui répond, et l’utilise.

Un diagramme à deux colonnes intitulées « client » et « server ». Une ligne horizontale en pointillés près du haut est intitulée « TCP connection already open ». En dessous, trois flèches montrent la poignée de main TLS : « ClientHello » du client au serveur, « ServerHello + certificate + key share » en retour, et « Finished » de nouveau vers l'avant. Une deuxième ligne horizontale en pointillés en dessous est intitulée « TLS session open -- everything after this is encrypted ». Deux épaisses flèches bidirectionnelles en dessous transportent des « encrypted data ».

La poignée de main TLS qu’exécute ssl.wrap_socket(). Elle s’appuie sur la connexion TCP déjà ouverte de la figure précédente ; une fois que les deux côtés ont envoyé Finished, le reste de la conversation est chiffré dans les deux directions.

Avertissement

Il s’agit de chiffrement seul, pas de TLS authentifié. La caméra communique de façon sécurisée avec quoi que ce soit qui a répondu à l’autre bout de la connexion TCP. Si un attaquant de type homme du milieu redirige la connexion vers un serveur qu’il contrôle et que ce serveur présente n’importe quel certificat, la poignée de main réussit tout de même et la caméra finit par communiquer de façon sécurisée avec l’attaquant. N’utilisez ce mode que lorsqu’un homme du milieu ne fait pas partie du modèle de menace – un réseau local fermé, un environnement de développement, la caméra communiquant avec un service exécuté sur le même matériel – et non lorsque vous accédez à l’Internet public.

Pour une authentification réelle – la caméra vérifiant un serveur public, la caméra agissant comme serveur TLS, ou un TLS mutuel – vous devez amener des certificats sur l’appareil. L’histoire complète se trouve dans Travailler avec les certificats TLS.

Le même enveloppement fonctionne pour le trafic TCP entrant, en sélectionnant le protocole serveur et en passant server_side=True à ssl.wrap_socket(). L’avertissement ci-dessus s’applique toujours : sans certificat propre, la caméra ne peut pas prouver qui elle est au client, et un client curieux verrait un échec de poignée de main « no certificate » sur la plupart des piles TLS. Le flux de travail des certificats côté production est ce qui débloque l’exécution de la caméra comme serveur TLS de manière utile.

9.17.3. Avec asyncio

Le chapitre asyncio a présenté asyncio.open_connection() pour les clients TCP simples. Ce même appel accepte un mot-clé ssl=True qui enveloppe la connexion dans TLS, là encore sans aucune configuration de certificat:

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 paire lecteur/écrivain derrière une connexion TLS a la même forme que pour une connexion TCP simple – seule la configuration diffère. La même réserve concernant l’authentification s’applique : ssl=True seul ne fournit que le chiffrement, pas la vérification.

9.17.4. DTLS – TLS sur UDP

TLS, tel qu’abordé jusqu’ici, repose sur TCP. Le protocole parallèle pour UDP est DTLS (Datagram TLS), et le module ssl de la caméra le prend en charge de la même manière. Là où TLS transforme une connexion TCP en un flux d’octets chiffré, DTLS transforme un socket UDP en un flux de datagrammes chiffrés, livrés individuellement – de sorte que les propriétés de perte / désordre / absence de contrôle de flux d’UDP vues dans UDP – envoyer un paquet et espérer pour le mieux se reportent toutes, les octets à l’intérieur de chaque datagramme étant désormais chiffrés.

L’enveloppement ressemble au cas de TLS, simplement avec un socket SOCK_DGRAM et les constantes du protocole 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()

(Appeler connect() sur un socket UDP n’ouvre pas de connexion – cela mémorise simplement une destination par défaut afin que les appels send() / recv() suivants n’aient pas à la répéter. DTLS a besoin de cette destination fixe pour exécuter sa poignée de main.)

La poignée de main a la même forme que le diagramme TLS ci-dessus ; la différence est que chaque message de poignée de main est lui-même un datagramme UDP, et chaque côté réémettra en cas de perte.

Note

La perte de paquets casse-t-elle le chiffrement ? Non. Chaque paquet DTLS porte un numéro de séquence, et le chiffrement utilise ce numéro pour produire une sortie différente pour chaque paquet – de sorte que la même entrée ne se chiffre jamais deux fois dans les mêmes octets, et que tout paquet peut être déchiffré seul sans que le précédent soit arrivé. Les paquets perdus ou désordonnés ne désynchronisent pas les deux côtés. (La poignée de main elle-même est la seule partie qui doit arriver de façon fiable, et DTLS gère cela avec sa propre retransmission.)

Le même avertissement « chiffrement seul sans certificats » donné plus haut s’applique : une poignée de main DTLS contre un pair en CERT_NONE chiffre le trafic mais ne vérifie pas qui est l’autre côté. Le flux de travail DTLS complet – certificats, le cookie anti-usurpation côté serveur, en quoi il s’agit de la même surface que TLS hormis les constantes de protocole – est traité aux côtés du matériel TLS dans Travailler avec les certificats TLS.

La version asyncio utilise le même schéma UDP non bloquant que dans Sockets avec asyncio. Effectuez la poignée de main de manière synchrone au départ, passez le socket en mode non bloquant, puis interrogez-le à l’intérieur d’une 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)

La poignée de main est le seul endroit où cette coroutine bloque la boucle d’événements ; après cela, chaque s.send / s.recv retourne immédiatement (ou lève OSError), et le await asyncio.sleep_ms maintient le reste du programme en marche.

9.17.5. Pour aller plus loin

Tout ce qui va au-delà du TLS à chiffrement seul – vérifier le certificat d’un serveur HTTPS public, exécuter la caméra comme serveur TLS authentifié, le TLS mutuel entre la caméra et un back-end, le choix des clés et des types de clés, la gestion de l’expiration des certificats – se trouve dans Travailler avec les certificats TLS. Cette section explique comment générer des certificats auto-signés pour des tests locaux, comment obtenir des certificats signés par une CA pour la production, comment les placer sur la caméra dans le bon format (DER), comment vérifier un serveur public lorsque la caméra est le client, comment penser la protection des clés sur un appareil qu’un attaquant pourrait démonter, et comment se préparer au jour où le certificat expirera.

Pour la référence complète de l’API ssl – versions de TLS prises en charge, suites de chiffrement et options de contexte – voir ssl — Module SSL/TLS.