miércoles, 21 de agosto de 2013

Anexo 19: funcionamiento detallado de Tor

Establecimiento e inicialización de circuito

La cadena de OR´s que forman un circuito es construida de forma telescópica, esto es, el OP negocia las claves de sesión con cada sucesivo OR. Una ventaja de esta forma de operar es que se dejen una vez que éstas claves de sesión se dejen de utilizar y sean borradas, aunque un nodo sea comprometido no se podrá descifrar células viejas (perfect forward secrecy). 

tor

El esquema se realiza con siguientes pasos:
  • El OP escoge un OR como exit node (ORn), de acuerdo con su exit policy.
  • EL OP escoge una cadena de OR's para constituir el path del circuito (OR1....ORn) de forma que ningún OR aparece dos veces en el path.
  • El OP abre una conexión TLS con el entry-node (OR1 ) si es que no la tenía abierta
  • El OP escoge aleatoriamente un ID de circuito (circID) que no se esté utilizando en la conexión con el entry-node
  • El OP inicia un intercambio de clave Diffie-Hellman con el entry-node envíando una celda CREATE
  • El entry-node le devuelve una celúla CREATED y de esta forma se obtiene una clave compartida Diffie-Hellman (D-H). De esta clave se derivan dos claves simétricas, una para cada sentido:
    • kf1 (forward key). Se usará en la comunicación OP->OR1 del circuito.
    • kb1 (backward key). Se usará en la comunicación OR1->OP.
  • A continuación el OP envía una petición a el OR1 para extender el circuito mediante una célula RELAY_EXTEND. Esta célula le indica al OR1 cual va a se el nuevo nodo OR2, su puerto y también contiene estructuras de datos que le tendrá que reenviar el OR1 al OR2 y que permitirán establecer un protocolo D-H de intercambio de claves entre el OP y OR2 a través de OR1 sin que el OR1 se entere de nada
  • El OR1 coge la célula y realiza el procedimiento Diffie-Hellman escogiendo él mismo un nuevo circID apropiado. Observar que ahora la células CREATE/CREATED se intercambian entre el OR1 y el OR2.
  • El OR1 manda al OP una célula RELAY_EXTENDED para mandarle lo que ha respondido el OR2 y así el OP tiene conocimiento de la clave D-H.
    La información está cifrada de tal forma que el nodo OR1 no puede acceder la información que se están intercambiando indirectamente el OP y OR2. De esta forma se establecen las nuevas claves simétricas: kf2 y kb2.
  • De forma similar se continúa hasta conseguir que el OP tenga establecidas las claves kf1,kb1,kf2,kb2,...,kfn,kbn con los respectivos nodos OR.

En cada establecimiento de clave Diffie-Hellman se obtiene un valor g^xy que es usado para calcular un valor K obtenido con la siguiente fórmula:
K=H( g^xy | [00] ) | H( g^xy | [01] )....| H( g^xy | [04] )
Donde:
  • | es la concatenación
  • [NN] es un byte cuyo valor es NN
  • H(x) es el SHA-1 de x
A partir de K se sacan una serie de valores derivados que se van usando en el proceso:
  • derivative key (KH): Primer HASH_LEN bytes. Usado en la respuesta del handshare para demostrar el conocimiento de la clave compartida.
  • digest forward (Df): Segundo HASH_LEN bytes. Es usado como semilla de la comprobación de integridad para flujos de datos desde OP a OR.
  • backguard digest (Db):Tercer HASH_LEN bytes. Es usado como semilla de la comprobación de integridad para flujos de datos desde OR a OP.
  • forward key (Kf): Es usado para encriptar los flujos de datos desde OP a OR.
  • backguard key (Kb): Es usado para encriptar los flujos de datos desde OR a OP.

Célula CREATE

Sobre el formato de la célula CREATE cabe señalar que el cifrado de g^x se hace de forma híbrida (con RSA-OAEP se cifra una clave de sesión y parte de g^x, con la clave de sesión se cifra el resto de g^x) para permitir que en una sola célula podamos hacer el intercambio del g^x completo.

Cuando inicializamos el primer salto de un circuito, el OP puede tener ya establecida la identidad del OR y negociado una clave secreta usando TLS. Por tanto no siempre es necesario para el OP realizar operaciones de clave pública para crear el circuito. En este caso el OP puede enviar una célula CREATE_FAST en lugar de una célula CREATE sólo para el primer salto. El OR responde con un CREATED_FAST, cuando el circuito está creado. La célula CREATE_FAST contiene una clave X generada aleatoriamente.

La célula CREATED_FAST contiene una clave Y generada aleatoriamente y un hash de la clave derivada construida a partir de X e Y. La clave compartida entre el OP y el OR será la concatenación de X e Y.

Por tanto cuando detectamos un CREATE_FAST podemos estar seguros de que el nodo OR es el primero del circuito. Por tanto los ORs deberían rechazar intentos de crear streams con RELAY_BEGIN (que sirven para indicar a los nodos que son exit node y por tanto tienen que enviar el contenido a cierta IP) saliendo del circuito en el primer OR. De esta forma se evitan cierto tipo de ataques.

Célula CREATED

El valor resumen de la clave establecida se manda para poder hacer una verificación de que ambos extremos de la comunicación comparten la misma clave.

Célula RELAY_EXTEND

Son de especial interés los siguientes campos:
  • La dirección (address) IPv4 y el puerto (port) del próximo OR del circuito.
  • El campo streamID vale 0 porque este tipo de células relay sirven para control.
  • El campo SHA-1(PCKS#1(PKOR1_ID)) es el hash SHA-1 del PKCS#1 con codificación ASN.1 de la clave de identidad (PKOR_ID) del próximo OR. Esto permite prevenir cierto tipo de ataques man-in-the-middle.

Observar que cuando el OR1 descifra con su clave AES obtiene distintos campos y entre ellos obtiene la siguiente piel de cebolla que puede utilizar directamente en la célula CREATE que tiene que construir para enviársela al OR2.

Célula RELAY_EXTENDED

Observar que consiste en la comunicación de la respuesta al protocolo Diffie-Hellman que hace el OR2 al OP usando al OR1 como intermediario sin que éste pueda inferir información alguna. El campo streamID vale 0 porque este tipo de células relay sirven para control.

Encapsulamiento de streams TCP

Una vez que un circuito se ha establecido se procede con el encapsulamiento de los streams TCP que contienen los datos de la aplicación. Un circuito ya establecido se puede aprovechar para multiplexar en él varios streams TCP provenientes de la misma o de distintas aplicaciones. De hecho, por eficiencia, Tor usa el mismo circuito para nuevos streams TCP durante diez minutos siempre que sea posible por la exit policy del exit node.
Una vez pasados los diez minutos las peticiones será dadas a un nuevo circuito. Esto mejora la protección y evita que un atacante vincule tráficos antiguos con los nuevos. El circuito permanecerá abierto hasta que todos los TCP streams que multiplexe hayan cerrado. Esta característica es necesaria para soportar muchos protocolos como SSH, los cuales son incapaces de mantener una sesión si se cambian los circuitos (y por tanto el exit node) durante la sesión. Los pasos para encapsular el tráfico TCP es el siguiente:
  • El OP escoge un circuito abierto que tenga un exit-node que le permita conectarse de la formada adecuada con el servidor. Si no existe lo crea según el algoritmo visto anteriormente.
  • El OP selecciona un StreamID arbitrario que no haya sido utilizado todavía en el circuito.
  • El OP construye una célula RELAY_BEGIN que contiene, cifrado adecuadamente para que sólo sea visto por el exit-node, la dirección y el puerto destino de la comunicación.
  • Esta célula es procesada por los nodos intermedios y el contenido útil es propagada hasta llegar en una célula RELAY_BEGIN que llega al exit-node.
  • El exit-node obtiene la dirección y el puerto destino e intenta abrir una nueva conexión TCP con el host destino. Pueden suceder dos cosas.
    • Si el exit-node no puede establecer la conexión devuelve una célula RELAY_END.
    • Si el exit-node sí se puede estalbecer la conexión entonces el exit-node devuelve una célula RELAY_CONNECTED que se irá propagando por la lista de OR´s hasta llegar al OP.
      Una vez llega la célula RELAY_CONNECTED a el OP, el OP y el exit-node empaquetan el stream de datos TCP en células RELAY_DATA.

Si un OP quiere cerrar un stream TCP anonimizado envía una célula RELAY_END la cual se propaga a lo largo del circuito y los nodos van cerrando ese stream para su circuito. Cuando llega al exit-node éste cierre la conexión TCP. El exit-node genera una célula RELAY_ENDED que se propaga hasta el OP.

Si el stream TCP que mantiene el exit-node con el destino se cierra de forma abrupta entonces el exit-node envía a lo largo del circuito hasta el OP una célula RELAY_TEARDOWN que indica el cierre inesperado de la conexión. Por el camino se van cerrando los streams de circuito asociados a stream TCP.

Una vez que un OR recibe una célula RELAY, éste chequea el circID de la célula y determina si tiene un circuito que se corresponda con esta a lo largo de la conexión. Si no lo tiene descarta la célula. Por otra parte si el OR no es exit-node, entonces cifra/descifra el carga útil del stream usando la claves kb/kf correspondiente con el OP. El OR entonces decide si reconoce la célula está completamente descifrada (ya visto más arriba como se decide). Si el OR reconoce la célula, la procesa. En otro caso la pasa a lo largo del circuito. Si el exit-node no reconoce una célula RELAY entonces ocurre un error y el OR envía una célula DESTROY para cerrar el circuito.

Cuando una célula RELAY llega al OP, el OP descifra la carga útil con kbi. Si la carga útil está completamente descifrada (ya visto más arriba como se decide) entonces procesa la carga útil.

Cierre y modificación de un circuito

Un circuito es cerrado cuando ocurre un error irrecuperable a lo largo del circuito o cuando l OP ve que todos los streams de un circuito están cerrados y el tiempo de vida del circuito ha terminado. Para cerrar un circutio el OR o el OP envían una célula DESTROY que se va propagando y cerrando los circID asociados al circuito.
Una vez que se ha establecido un circuito y se está utilizando el OP puede decidir truncarlo (por ejemplo porque se ha caído) y crear uno nuevo a partir de donde trunque. Se procede con los siguientes pasos:
  • El OP escoge el OR donde se va a truncar y el OR nuevo que se quiere usar en el path.
  • El OP construye una célula RELAY_TRUNCATE que llega hasta el OR a partir del cual se va a truncar.
  • El OR desde el que se trunca comienza un proceso de cierre de circuito enviando una célula DESTROY.
  • El OR desde el que se trunca lanza una célula RELAY_TRUNCATED hacia el OP.
  • El OP cuando le llega el RELAY_TRUNCATED envía un RELAY_EXTEND para construir el nuevo circuito.
Cuando un error irrecuperable ocurre a lo largo de la conexión de un circuito, el OR debería, si está habilitado para ello, enviar al OP una célula RELAY_TRUNCATED; el nodo más lejano basta con enviar una célula DESTROY para cerrar el circuito.

También se cierra el circuito por parte de los ORs cuando ha habido algún error irrecuperable a lo largo del circuito.


La carga útil de las células DESTROY o RELAY_TRUNCATED contienen sólo un octeto que indican el motivo por el que el circuito debería ser cerrado o truncado. Este motivo es propagado a lo largo de los nodos.

0 comentarios:

Publicar un comentario