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 :
- D'assurer le chiffrement des données, et donc leur confidentialité. Seuls les deux noeuds sont capables de lire les données échangées
- D'assurer l'intégrité des données échangées. Une modification des données échangées par un tiers n'est pas possible
- D'assurer l'authentification du prochain noeud dans le circuit. En effet les serveurs d'annuaire présente le certificat des ORs, permettant d'assurer leur identité
Chaque OR dispose de plusieurs clés qui seront utilisées différemment :
- Une identity key ou clé d'identité. Cette clé a une durée de vie importante, elle n'est pas modifiée pendant l'exécution de l'OR. Elle est utilisée pour signer son certificat TLS et son router descriptor qui résume ses clés, son adresse, sa politique de sortie, sa bande passante dans les serveurs d'annuaire. Ces serveurs d'annuaire disposent également d'une identity key qu'ils utilisent pour signer l'annuaire.
- Une Onion Key qui est utilisée lors de la construction de circuits.
- De clés éphemères négociées lors de la construction de circuits.
- De clés de liens négociées pour chaque connexion TLS.
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.

Les types de commandes possibles sont les suivants :
- PADDING utilisé pour le keep-alive
- CREATE/CREATED qui permettent de créer un circuit et d'acquitter de sa connexion
- DESTROY utilisé pour détruire un circuit
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.
Les types de commandes possibles sont les suivants :
- RELAY DATA utilisé afin de faire suivre des données
- RELAY BEGIN/RELAY CONNECTED pour créer un flux et acquitter de sa création
- RELAY END pour fermer un flux proprement
- RELAY EXTEND/RELAY EXTENDED pour étendre le circuit d'un saut et acquitter de l'extension
- RELAY TRUNCATE/RELAY TRUNCATED pour fermer une partie du circuit et acquitter de la fermeture
- RELAY SENDME utilisé pour le contrôle de congestion
- RELAY TEARDOWN pour fermer un flux qui ne répond plus
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.

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 :
- Les deux parties A et B se mettent d'accord sur un chiffre g
- A envoie le résultat de g^x à B, x étant un chiffre choisi aléatoirement et connu uniquement de A
- B envoie le résultat de g^y à A, y étant un chiffre choisi aléatoirement et connu uniquement de B
- Les deux parties A et B sont alors capables de calculer la clé même clé symétrique
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 :
- Alice envoie sa première partie de la négociation chiffrée. Ceci d'assurer deux choses. La première, est une authentification unilatérale. Dans l'exemple, Alice sait qu'elle a eu affaire à OR 1 car elle a utilisé sa clé publique pour chiffrer des données que seul lui peut déchiffrer grâce à sa clé privée. OR 1 ne connait cependant pas l'identité d'Alice car aucune clé lui appartenant n'a été utilisée. La seconde chose assurée est la confidentialité de la clé symétrique échangée. En effet, le recours au chiffrement dans cette situation permet d'éviter des attaques du type Man-In-The-Middle auxquelles la négociation Diffie-Hellman est vulnérable.
- OR 1 envoie un condensat de la clé symétrique qu'il a calculé. Ceci prouve que c'est bien lui qui a reçu la première partie de la négociation, g^x1, mais aussi que c'est bien lui qui a choisi y.
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 :
- le type de la commande à exécuter, ici EXTEND, qui indique à OR 1 qu'il devra étendre le circuit
- le noeud vers lequel le circuit doit être étendu, OR 2
- la première partie de la négociation Diffie-Hellman entre Alice et OR 2
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 :

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 :
- Open exit nodes : ces noeuds permettent de sortir vers des serveurs externes
- Middleman nodes : ces noeuds ne peuvent être utilisés que pour construire des circuits, ils ne peuvent pas être utilisés comme point de sortie
- Private exit nodes : ces noeuds ne permettent l'accès qu'à des réseaux locaux
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.

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 :
- La packaging window qui représente la quantité de cellules que le noeud est prêt à relayer vers l'OP depuis un flux TCP
- La delivery window qui représente la quantité de cellules relayables vers un flux TCP
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.