Anonymisation de connexions TCP grâce à TOR

Coeur de l'oignon

Les circuits en détail

Nous avons vu dans la précédente partie qu'un circuit était établi pour accéder à la destination voulue. Nous n'avions alors pas détaillé la construction et le fonctionnement de ce circuit. Avant d'aborder leur réalisation technique, précisons quelques points.

La construction d'un circuit est coûteuse, ceci à cause des mécanismes mis en place pour assurer la sécurité du circuit établi. Afin de limiter cet impact sur l'utilisation, et à la place de créer un circuit par flux de données, donc par application, plusieurs flux de données sont multiplexées sur un même circuit. Ce circuit est changé périodiquement, toutes les 10 minutes, afin de limiter la fenêtre de temps qui permettrait à un attaquant d'identifier l'utilisateur.

Les circuits sont créés avant leur utilisation, afin de limiter une latence dû à la construction du circuit lors de sa première utilisation. Cela évite les délais au moment du besoin de circuit, et permet d'impacter de façon minimale sur l'utilisation de TOR.

Les OPs (les clients) construisent de nouveaux circuits périodiquement, nous l'avons vu. Les circuits ne sont supprimés que s'ils ont été utilisés. Lorsque TOR n'est pas utilisé, de nouveaux circuits ne sont pas établis. La construction de circuit se fait en parallèle de l'utilisation, afin d'assurer une meilleure qualité de service : lorsque la construction d'un nouveau circuit échoue, l'utilisateur continue d'utiliser le précédent.

Une autre caractéristique des circuits est qu'il présente une architecture dite de leaky pipe. Les noeuds de sortie (noeuds qui sont responsables de contacter le serveur destinataire que le client souhaite joindre) peuvent se trouver à n'importe quel niveau du circuit.

Introduction au chiffrement

En fait, les connexions présentes entre chaque noeud du circuit ne sont pas de simples connexions. Ce sont des connexions TLS, qui permettent :

Chaque OR dispose de plusieurs clés qui seront utilisées différemment :

Les cellules

Nous n'en avons pas parlé jusqu'à maintenant, mais la communication au sein du réseau TOR se fait par des cellules de taille fixe de 512 octets. Chaque circuit, au niveau d'un noeud est identifié par un identifiant unique, qui est précisé dans la cellule. Ce besoin d'identifier le circuit concerné s'explique par le fait que plusieurs circuits peuvent être multiplexés sur une même connexion TLS. Un même circuit présentera plusieurs identifiants différents entre chaque noeud.

Il y a deux types de cellules. Les cellules de contrôle, et les cellules de relai.

Les cellules de contrôle

Les cellules de contrôle sont interprétées par les noeuds qui les reçoivent. Elles présentent comme toutes les cellules l'identifiant CircID qui précise le circuit concerné, ainsi qu'une commande définie par le champs CMD et des données supplémentaires dans le champs DATA.

Structure d'une cellule de contrôle

Les types de commandes possibles sont les suivants :

Les cellules de relai
Les cellules de relai contiennent des données de flux bout-en-bout. Leurs 3 premiers octets sont similaires à celles des cellules de contrôle (le champs CMD des cellules de contrôle comprenant alors une valeur indiquant qu'il s'agit d'une cellule de relai), mais les octets suivants présentent des données d'en-tête supplémentaire. Le champs StreamID permet de définir quel flux est concerné par la cellule, le champs Digest permet d'effectuer du contrôle d'intégrité, le champs LEN précise la taille des données contenues dans le champs DATA et le champs CMD fournit un type de commande à exécuter. Structure d'une cellule de relai

Les types de commandes possibles sont les suivants :

Ouverture de flux côté OP

On parle de flux lorsqu'une application a besoin d'une connexion TCP. L'application de l'utilisateur, lorsqu'elle a besoin de se connecter à un serveur externe, demande à l'OP (au client logiciel), de lui fournir une connexion. Cette demande se fait en utilisant SOCKS.

L'OP choisit le dernier circuit créé et choisit un noeud, un OR, comme point de sortie dans ce circuit. Il envoie alors une cellule de relai de type BEGIN, avec un nouvel identifiant de flux aléatoire. Le noeud de sortie répond par une cellule de relai de type CONNECTED après avoir initialisée la connexion TCP avec le serveur externe.

Enfin, l'OP notifie l'application, via SOCKS, de la création du flux. L'OP est alors capable de relayer les données vers le point de sortie en utilisant des cellules de relai de type DATA.

Construction de circuits

Un circuit dans TOR est construit de façon incrémentale. Cela signifie qu'il est construit tronçon par tronçon, en commençant par sa source. Des clés symétriques sont générées pour chaque noeud constituant le réseau. Seule la source connait l'intégralité de ces clés symétriques, dites Onion Keys, chaque noeud pour lequel la clé ayant été générée la connaissant également.

Pour construire un circuit, les deux types de cellules sont utilisés.

Nous allons voir un exemple où Alice essaye de joindre un site Web, Website, à l'aide d'un circuit composé des noeuds OR 1 et OR 2. La première chose à remarquer est la création de connexion TLS entre chaque noeuds voisins. En effet, au sein du réseau TOR, une connexion TLS est établie pour chaque communication de proche en proche. Cet établissement de connexion n'est pas détaillé par le schéma illustrant l'exemple, mais il n'y a qu'une chose à retenir : lorsqu'un noeud désire joindre directement un autre noeud, il utilise la connexion TLS entre lui-même et ce noeud si elle existe, ou la crée sinon.

Exemple de construction d'un circuit

1) Dans un premier temps, Alice va créer un circuit qui se terminera à OR 1. Elle va commencer par envoyer une cellule de contrôle CREATE demandant la création d'un circuit entre Alice et OR 1 d'identifiant c1. Cette cellule contient la première partie de la négociation Diffie-Hellman, g^x1.

Pour rappel, une négociation Diffie-Hellman permet la génération d'une clé symétrique entre deux interlocuteurs. Dans cette négociation, la clé n'est pas envoyée en clair, mais chaque partie va échanger le résultat d'un calcul qui permettra à chacun de calculer la clé symétrique négociée. L'algorithme en lui-même n'étant pas le plus important ici, nous ne retiendrons que ce qui suit :

Alice envoie donc g^x1, la première partie de la négociation Diffie-Hellman pour sa clé symétrique qui ne sera connue que d'elle et d'OR 1. Cette première partie est envoyée au sein d'une cellule CREATE, et est chiffrée en utilisant la clé publique d'OR 1, permettant d'assurer que seul celui-ci peut la lire. OR 1 répond alors par une cellule CREATED qui contient la deuxième partie de la négociation, g^y1, ainsi qu'un condensat (un hash) de la clé symétrique calculée. A ce moment, Alice et OR 1 sont capables de générer une clé symétrique commune à partir des données choisies et reçues.

Deux choses peuvent être notées :

A l'issue de cette première étape, et suite à cette échange de cellules de contrôle CREATE/CREATED, un circuit a été créé. Ce circuit a alors pour origine Alice et pour fin OR 1. Chacun de ces deux noeuds elle alors capable d'identifier ce circuit, grâce à l'identifiant c1 qui est unique dans la connexion Alice/OR 1. Ils sont également les seuls à connaître l'Onion Key qu'ils ont établi lors de la négociation Diffie-Hellman.

2) Le premier tronçon du circuit ayant été construit, Alice demande à OR 1 d'étendre le circuit d'un saut. Pour cela, elle utilise une cellule de type relai, qu'elle indique comme étant destinée au circuit c1. Les données (tout sauf les 3 premiers octets, donc tout sauf l'identifiant du circuit et l'octet indiquant qu'il s'agit d'une cellule de relai) contenues dans cette cellule sont chiffrées grâce à l'Onion Key précédemment négociée entre Alice et OR 1.

Les données contenues dans cette cellule de relai comprennent :

OR 1, à réception de cette cellule de relai, ne sait pas directement que la commande lui est destinée. Il va d'abord déchiffrer les données avec sa clé, puis vérifier le champ Digest et sa cohérence par rapport aux données transportées. Ici, il va constater que la valeur de Digest correspond bien à la somme de contrôle des données, et traiter la commande EXTEND.

Pour ce faire, il va créer le tronçon entre lui et OR 2 de la même façon qu'Alice avait créé le tronçon entre elle et lui. Il va utiliser une cellule de contrôle de type CREATE, en fournissant un identifiant c2 qu'il sait unique entre lui et OR 2, et fournir la première partie de la négociation Diffie-Hellman fournie par Alice dans la cellule de relai qu'il a reçue, soit g^x2 chiffrée avec la clé publique d'OR 2. Cette cellule est suivie d'une réponse sous la forme d'une cellule de contrôle CREATED, indiquant la deuxième partie de la négociation Diffie-Hellman que seule Alice pourra exploiter puisque la première partie n'est connue que d'elle et OR 2.

Afin de communiquer à Alice la réponse d'OR 2, OR 1 utilise une cellule de relai dont les données chiffrées correspondent à une commande de type EXTENDED, acquittant de l'extension du circuit, et faisant suivre les informations fournies par OR 2 concernant la fin de la négociation Diffie-Hellman.

Le circuit qu'Alice avait choisi de construire est alors opérationnel. Elle partage avec OR 1 une première Onion Key, et une deuxième avec OR 2. C'est dans la prochaine étape que nous allons mettre en valeur la notion d'épluchage d'oignon qui a valu à ce réseau ce nom.

3) Alice souhaite maintenant établir un flux afin de pouvoir accéder à son site Web de destination. Elle veut donc communiquer à OR 2 l'ouverture de ce flux. Afin de garantir son anonymat, elle va chiffrer itérativement les données de la cellule de relai qu'elle va envoyer à OR 2, d'abord avec l'Onion Key qu'elle partage avec OR 1, puis avec celle qu'elle partage avec OR 2. Les données de la cellule de relai, en parcourant le circuit, seront successivement déchiffrées par les noeuds rencontrés, soit, dans l'ordre, OR 1 puis OR 2. C'est de cette succession de déchiffrement que vient l'analogie à l'oignon, que l'on épluche par couche.

Ainsi lorsque la source d'un circuit va vouloir envoyer des données à l'autre bout du circuit, on obtiendra, de façon imagée, ceci :

Diagram explaining the onion routing principle. Rasterized by fenrisfox into PNG format, from a SVG file released under the GFDL by Harrison Neal, 12 March 2008

Il s'agit bien évidemment d'une image : les couches ne sont pas ajoutées, puisqu'il s'agit de chiffrement successifs.

La cellule de relai envoyée par Alice contient des donnés indiquant qu'il s'agit d'une commande BEGIN, et que le serveur externe à contacter est situé à l'adresse <website> (qui pourrait être www.google.fr), sur le port 80. Lorsqu'OR 2 reçoit cette cellule, il constate que sa commande lui est destinée (la somme de contrôle étant vérifiée), et initie une connexion TCP vers <website>. Une fois cette initialisation terminée, il répond à Alice par une commande CONNECTED au sein d'une cellule de relai. L'acheminement de cette cellule étant vers la source du circuit, les données sont chiffrées par chaque noeud traversés au lieu d'être déchiffrées. Alice, à la réception de la cellule, déchiffre alors itérativement les données pour prendre connaissance de la réussite de l'ouverture de la connexion TCP.

4) La connexion TCP étant initialisée, Alice veut accéder à des données sur le serveur externe. Elle va donc envoyer une cellule de relai avec pour commande DATA, et les données TCP à faire parvenir au serveur externe. Le cheminement de cette cellule de relai est le même que celui de la cellule de relai envoyée par Alice à l'étape 3 : Alice commence par chiffrer itérativement les données, et à chaque noeud traversé, un déchiffrement est opéré.

La cellule ayant atteint OR 2, celui-ci fait parvenir les données au serveur externe et achemine progressivement la réponse fournie par le serveur, en utilisant lui aussi une cellule de relai de type DATA contenant cette réponse.

Les Directory Servers

Les Directory Servers sont des noeuds spécifiques qui ont le rôle d'annuaires au sein du réseau. Ils sont redondants, et présentent les mêmes informations. Leur fonction est de référencer les ORs connus, en présentant leur Router Descriptor. Ces descriptors regroupent des informations telles que ses clés publiques, sa politique de sortie (ou exit policy), son adresse réseau, sa bande passante, etc.

Les OPs doivent vérifier la cohérence des données présentées, en validant la similitude des données présentées sur d'autres annuaires. Ces annuaires sont disponibles en HTTP, et sont utilisés pour bootstrapper les clients. Le bootstrapping est une nécessité dans ce genre de réseau : il permet aux clients d'initialiser leur vue du réseau, et les noeuds qu'ils connaissent.

Un exemple de serveur d'annuaire disponible : http://moria.seul.org:9032/tor/status/authority. Afin de vérifier son fonctionnement, j'ai créé un serveur TOR sur ma machine avec pour nom xposeIG2K. Au bout de quelques minutes, mon serveur était référencé dans cet annuaire (ceci est un extrait de l'annuaire affiché lors de l'accès à la page précédemment citée) :

r xposeIG2K 4P7wXEQ57as+boBhCuCIn0fyvzA laBwFecFIQnUW1deF2D1W0tBSng
2010-01-11 18 :50 :42 85.171.182.65 9001 0
s Exit Fast Running Valid
opt v Tor 0.2.1.20

Par curiosité, nous pouvons compter les noeuds disponibles afin d'avoir une idée de la capacité du réseau :

Exemple
~/xpose $ cat authority |grep Running|wc -l
1754

Exit policies ou politiques de sortie

Afin d'améliorer le confort d'utilisation des personnes souhaitant mettre en place des serveurs TOR, des politiques de sortie ont été mises en place. L'idée étant de permettre de restraindre les opérations possibles à partir de ce noeud que représente le serveur.

Des types d'ORs (du moment que l'on souhaite être serveur TOR, on incarne un OR) ont été définis :

D'autres paramètres de configuration sont également possibles, comme la restriction de services. Par défaut, le port SMTP (25) est bloqué pour éviter l'utilisation du réseau pour des pratiques de mail spamming.

Ces paramètres permettent par exemple de se prévenir de l'utilisation de son serveur au sein d'un réseau p2p, évitant une mise en responsabilité du point de vue légal.

Contrôles réseaux

Certains contrôles sont souvent souhaitables au niveau d'un réseau. Parmis ces contrôles, on peut citer les contrôles d'intégrité, les contrôles de congestion ou encore les contrôles de débit. Nous avons déjà parlé du contrôle d'intégrité, notamment grâce au champs Digest des cellules de relai qui permettent d'assurer l'intégrité des cellules. Ainsi lorsque la somme de contrôle calculée par chaque noeud n'est jamais égal à la valeur de champ, le dernier noeud a la responsabilité de jeter ce paquet et de fermer le circuit.

Contrôle du débit

Nous avons parlé de la présence d'une valeur de bande passante dans les Router Descriptor associés aux ORs. Effectivement, il est possible, au niveau d'un serveur TOR, de spécifier une bande passante maximale à utiliser, pour minimiser la charge du serveur.

Cette limitation se fait en utilisant un token bucket. Cette technique consiste à modéliser la bande passante disponible sous forme de seau, où régulièrement sont versés des jetons, qui peuvent s'accumuler jusqu'à une certaine limite, et d'où les jetons sont tirés à chaque donnée transitée. Le débit moyen s'en trouve limité, tout en permettant des bursts, c'est-à-dire des montées de débit temporaires, pour répondre à des demandes à des instants bien précis.

Modélisation token bucket

Ce contrôle permet de fournir un service préférentiel aux services interactifs, qui n'ont pas besoin de beaucoup de débit mais d'une latence faible. La fréquence des cellules envoyées pour le stream concerné est alors observée, "classifiant" le service d'interactif en dessus d'une certaine fréquence.

Contrôle de congestion

La congestion est une situation d'utilisation du réseau défavorable où trop d'activité sur les connexions amène à leur détérioration en termes de qualité de service. Elle peut arriver par exemple lorsque trop d'utilisateurs utilisent un même OR, rendant sa bande passante insuffisante et faisant apparaître un goulot d'étranglement. TOR utilisant le protocole TCP, le séquencement et le renvoi sont délégués à celui-ci. Une partie du problème est donc géré par TCP.

L'autre partie du problème est gérée au sein du réseau TOR d'une manière similaire à TCP, en utilisant des fenêtres. En effet, un problème se présente dans la mesure où la bande passante doit être régulée au niveau global, au niveau du circuit. TCP ne peut pas être utilisé directement pour cela. Cet aspect de la congestion n'est appliqué que pour les cellules de relai de type DATA : ce sont elles qui génèrent le plus de trafic.

Deux niveaux de congestion sont identifiés : au niveau des circuits, et au niveau des flux. Dans les deux cas, on utilise des fenêtres à la manière de TCP. Expliqué simplement, ce processus se résume à suivre le nombre de cellules que l'on est prêt à envoyer. On distingue donc deux fenêtres :

Lorsqu'une cellule est envoyée, la fenêtre correspondante est décrémentée. Lorsque la fenêtre est vide, plus aucune cellule n'est envoyée. Lorsque suffisamment de cellules ont été reçues, une cellule de relai de type SENDME est envoyée. A la réception de cette cellule, la fenêtre est augmentée d'un nombre arbitraire. Lorsque la fenêtre concernée est celle des circuits, un identifiant de flux de 0 est utilisé. Lorsque la fenêtre concernée est celle des flux, le noeud attend le flush sur le flux TCP avant de comptabiliser la cellule comme reçue.