Categorías
Hacking Networking Services - Software

Preservando el Anonimato y Extendiendo su Uso – Hacking I2P: Desarrollo de Aplicaciones usando Streaming Library – Parte XXXII

NOTA PREVIA: Se asume que el lector cuenta con conocimientos sobre programación (especialmente sobre Java) para comprender esta entrada.
I2P no solamente permite a sus usuarios utilizar aplicaciones ya existentes sobre la capa de aplicación, permite además, que cualquiera pueda escribir aplicaciones utilizando la API de Java disponible para tal fin, se trata de una API que se encuentra en constante desarrollo y evolución, en cada nueva versión surgen nuevas clases e interfaces que resultan bastante llamativas, sin embargo siempre siguen la misma jerarquía de clases principales y la misma estructura que se utiliza para realizar las tareas comunes de conexiones y establecimiento de comunicación entre usuarios.


Antes de comenzar con la explicación correspondiente a las clases e interfaces principales así como los ficheros JAR necesarios para comenzar a desarrollar de forma rápida con I2P, es necesario entender cuando es conveniente utilizar esta API y cuando no.
En primer lugar, todas las aplicaciones que se ejecutan en I2P utilizan un puente, que como se recordará de anteriores publicaciones, se trata habitualmente de I2PTunnel, el cual se encarga de conectar los túneles cliente y servidor de la instancia de I2P. Este modelo resulta conveniente y deseable cuando se sigue un modelo cliente-servidor, dado que en dicho modelo solamente se utiliza UNA única instancia de I2PTunnel para conectar a N clientes con un solo servidor, ejemplos de esto se han visto anteriormente con EEPSITES, Servicios remotos como SSH, Telnet, etc. Donde se debía crear un túnel cliente y/o servidor para acceder a dichos servicios. No obstante cuando se trata de aplicaciones peer-to-peer donde la comunicación no esta centralizada en un solo nodo, sino que cualquier participante puede actuar como emisor o receptor en un momento dado (aplicaciones de mensajería instantánea, por ejemplo) resulta inviable utilizar I2PTunnel, ya que se necesitaría una nueva instancia de “servidor” (realmente, una nueva instancia de peer) por cada emisor de un mensaje. Es en estos momentos en los que se hace necesario emplear la API de I2P, ya que si se utiliza I2PTunnel en tales situaciones, lo más probable es que el consumo de recursos y de memoria sea simplemente inviable para el rendimiento general de un sistema.
Para comprender correctamente los elementos fundamentales del desarrollo de aplicaciones utilizando la API Java de I2P, es necesario comprender las clases principales que se deben involucrar para crear “Destinations” y posteriormente poder consultarlos por otros clientes de I2P, por este motivo se van a incluir los pasos necesarios para crear Destinations I2P que jueguen el rol de “servidores” y Destinations I2P que jueguen el rol de “clientes”.
I2P Destination Server

  1. En primera instancia las clases I2PSocketManager, I2PSession y I2PSocketManagerFactory son las clases principales para darle acceso a un determinado objeto a la instancia de I2P que se encuentra en ejecución en la máquina local. Es necesario que I2P se encuentre levantado, dado que estas clases permiten acceder a una sesión I2P y crear un Socket Servidor (este objeto en I2P es el equivalente a un objeto ServerSocket de Java para declarar que una máquina se encuentra en estado de “escucha” por un determinado puerto y acepta peticiones de otras máquinas a las que se les denomina “clientes”). Utilizando la API de Java esta primera interacción con I2P se puede resumir con las siguientes lineas:[sourcecode language=»java»]
    //En primer lugar se intenta crear un Administrador de Sockets I2P.
    I2PSocketManager manager = I2PSocketManagerFactory.createManager();

    //Utilizando el administrador, se crear un ServerSocket I2P.
    I2PServerSocket serverSocket = manager.getServerSocket();

    //Utilizando el administrador, se crea una Session I2P para obtener el //Destination en Base64 que será el punto de acceso de clientes que se //deseen conectar al ServerSocket creado de forma anónima.
    I2PSession session = manager.getSession();

    //Para conocer el Destination asignado a la Session creada por I2P, se //consulta el método “getMyDestination()
    System.out.println(session.getMyDestination().toBase64());
    [/sourcecode]

  2. Ahora que se ha creado un ServerSocket I2P y se cuenta con un Destination valido para que los clientes puedan contactar con la aplicación de forma anónima, es necesario seguir el mismo modelo que se sigue con la programación de Sockets en Java, a saber, es necesario crear un hilo independiente para cada uno de los clientes que contacte con la aplicación, esto es importante, dado que normalmente las aplicaciones que se exponen a usuarios externos tienen unos mínimos de concurrencia y si no se implementa un hilo por cada cliente conectado, el Socket servidor solamente podrá atender a un cliente a la vez, lo que sin lugar a dudas se traducirá en un cuello de botella. En I2P, existe la clase I2PThread que es útil precisamente para tal fin, su funcionamiento es similar al de cualquier Thread en Java, se debe instanciar y establecer un objeto que implemente la interfaz Runnable

[sourcecode language=»java»]

//Se crea un objeto I2PThread especificando una implementación de la interfaz //Runnable que se encargará de sobre-escribir el metodo "run" del Thread.
I2PThread client = new I2PThread(new RunnableClient(serverSocket));

//Se establecen algunas propiedades básicas del hilo y posteriormente se inicia su ejecución
client.setName("client");
client.setDaemon(false);
client.start();
[/sourcecode]

  1. Los elementos anteriores representan la implementación básica de un ServerSocket I2P para escuchar a cualquier petición de clientes de forma anónima. Ahora es el momento de la parte “pesada” la implementación de la lógica de cada uno de los hilos del servidor, esta lógica incluye la lectura y escritura de los flujos de entrada y salida del socket, así como cualquier otra tarea que lleve a cabo la aplicación. Para mantener las cosas simples y entendibles, simplemente se recibirá cada petición y se responderá con un mensaje de “bienvenida”, el código de dicha lógica se define en la clase RunnableClient que es la que anteriormente se ha utilizado la creación del objeto I2PThread.[sourcecode language=»java»]
    //Clase encargada de iniciar un hilo por cada cliente que contacte con la aplcación (Server Socket)
    public class ClientHandler implements Runnable {

    //Server Socket obtenido del Manager de I2P.
    private I2PServerSocket socket;

    //Constructor que recibe como parámetro el Server Socket
    public ClientHandler(I2PServerSocket socket) {
    this.socket = socket;
    }

    //Método encargado de ejecutar la lógica necesaria para procesar cada //petición recibida en el Server Socket.
    public void run() {
    //Se inicia ciclo infinito para recibir clientes.
    while(true) {
    try {
    //Se acepta cada una de las peticiones entrantes y se //obtiene un objeto I2PSocket que será el que implemente los flujos de entrada y salida necesarios para la //comunicación con el cliente
    I2PSocket sock = this.socket.accept();

    if(sock != null) {
    //Se obtiene el flujo de entrada del socket, este flujo contiene la información que el cliente ha enviado.
    BufferedReader br = new BufferedReader(new InputStreamReader(sock.getInputStream()));

    //Se obtiene el flujo de salida del socket, este flujo permite enviar información al cliente.
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream()));

    //Se lee cada una de las lineas enviadas por el cliente.
    String line = “”;
    while(br.readLine() != null) {
    System.out.println("Info. From client: " + line);
    }

    // Se envía un mensaje al cliente.
    bw.write("I’ve got your message… bye! ");

    //Se limpia el flujo para garantizar el correcto envío del mensaje
    bw.flush();

    //Se cierra el socket.
    sock.close();
    }
    } catch (I2PException ex) {
    System.out.println("General I2P exception!");
    } catch (ConnectException ex) {
    System.out.println("Error connecting!");
    } catch (SocketTimeoutException ex) {
    System.out.println("Timeout!");
    } catch (IOException ex) {
    System.out.println("General read/write-exception!");
    }
    }
    }
    }
    [/sourcecode]

Como se puede apreciar del código anterior, por cada petición recibida se obtiene un objeto I2PSocket, que es el objeto que realmente permite la comunicación entre ambas máquinas (emisor y receptor) de forma anónima para ambas partes. Aunque se trata de un fragmento de código muy simple, ilustra perfectamente la filosofía que se debe seguir cuando se escribe una aplicación con I2P y Streaming Library, aquí ahora esta en juego la “creatividad” del desarrollador para crear aplicaciones e implementar la lógica que desea. A continuación se describe el código necesario para implementar la lógica por parte de un cliente I2P para conectarse con una aplicación remota de forma anónima.
I2P Destination Client

Ahora que se tiene un ServerSocket esperando conexiones, es el momento de implementar la lógica por parte del cliente, para ello se lleva a cabo el siguiente procedimiento estándar.

  1. Del mismo modo que se han utilizado las clases I2PSocketManager y I2PSocketManagerFactory en la implementación del lado del receptor (servidor), en el caso del emisor de mensajes (cliente) también es necesario acceder a un manager para obtener un objeto Socket. Sin embargo, esto no es todo ya que en este caso, el procedimiento de creación es distinto, debido a que se debe involucrar un objeto adicional, el objeto Destination que se incluye en la API Java de I2P. Este objeto simplemente representa la cadena de texto en Base64 del ServerSocket, evidentemente es necesario que el ServerSocket anteriormente iniciado comunique de alguna forma a sus posibles clientes su existencia, por ello debe suministrar el Destination que ha generado la instancia de I2P para que después pueda ser utilizado por un cliente y realizar una conexión. El mecanismo de entrega de este Destination es irrelevante a efectos de programación, ya que puede ser tan simple como enviar un correo electrónico a los clientes con dicha cadena de caracteres en Base64 o registrar dicho servicio (aplicación I2P) en un servicio de directorio como el “stats” de registro de I2P (ver la entrada correspondiente a la publicación de EEPSITES y servicios SSH de esta serie de publicaciones para mayor información). Para mantener las cosas simples, se leerá desde la consola la cadena de caracteres correspondientes al Destination del ServerSocket. El código implementado para hacer estas funciones es el siguiente.[sourcecode language=»java»]
    //Se obtiene una instancia de un SocketManager de I2P.
    I2PSocketManager manager = I2PSocketManagerFactory.createManager();

    //Se solicita e ingresa la cadena de texto correspondiente al Destination de I2P
    System.out.println("Please enter a Destination:");
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String destinationString = null;
    try {
    destinationString = br.readLine();
    } catch (IOException ex) {
    System.out.println("Failed to get a Destination string.");
    return;
    }
    Destination destination = null;
    try {

    destination = new Destination(destinationString);
    I2PSocket socket = null;
    socket = manager.connect(destination);
    } catch (I2PException ex) {
    System.out.println("General I2P exception occurred!");
    } catch (ConnectException ex) {
    System.out.println("Failed to connect!");
    } catch (NoRouteToHostException ex) {
    System.out.println("Couldn’t find host!");
    } catch (InterruptedIOException ex) {
    System.out.println("Sending/receiving was interrupted!");
    } catch (DataFormatException ex) {
    System.out.println("Destination string incorrectly formatted.");
    return;
    }
    [/sourcecode]

  2. Las lineas de código anteriores, resultan casi auto-explicativas, dado que en primer lugar se lee el Destination al que se desea conectar el cliente y posteriormente se procede a realizar una conexión si no se ha producido ningún problema. Ahora es el momento en el que el cliente comienza a enviar mensajes al Server Socket, para ello se emplea el siguiente fragmento de código[sourcecode language=»java»]
    try {
    //Utilizando el flujo de salida se envían datos al servidor.
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
    bw.write("Hello ServerSocket!\n");
    bw.flush();

    //Se obtiene el flujo de entrada para leer la respuesta del ServerSocket.
    BufferedReader br2 = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    String s = null;
    while ((s = br2.readLine()) != null) {
    System.out.println("Received from server: " + s);
    }
    //Se finaliza la comunicación con el ServerSocket.
    socket.close();
    } catch (IOException ex) {
    System.out.println("Error occurred while sending/receiving!");
    }
    [/sourcecode]

Este es todo el código necesario para comunicar dos aplicaciones I2P utilizando la API de Java Streaming Library, el código anterior es muy simple y no resalta ningún tipo de “lógica” que se adapte a una aplicación funcional, sin embargo representa un primer paso para escribir aplicaciones I2P y acceder a un servicio en I2P de forma anónima.
NOTA: En este ejemplo se han utilizando dos clases Java que corresponden al cliente y al servidor, sin embargo no siempre puede ser este el modelo utilizado por un desarrollador, ya que también es posible utilizar SAMV2/V3 o BOB como “ServerSockets” y escribir un programa que actué como cliente para dichas implementaciones, de hecho el código presentado aquí es perfectamente valido para tal fin, dado que lo único que se necesita es el Destination del ServerSocket y este podría ser BOB. En la próxima entrada se explicará que es BOB y como funciona para que esta aclaración cobre un mayor sentido para el lector.

Por Daniel Echeverri

Formador e investigador en temas relacionados con la seguridad informática y hacking. Es el autor del blog thehackerway.com el cual ha sido el ganador del European Cybersecurity Blogger Awards 2021 en la categoría de “Best Technical Content“.

3 respuestas a «Preservando el Anonimato y Extendiendo su Uso – Hacking I2P: Desarrollo de Aplicaciones usando Streaming Library – Parte XXXII»

Ehh!! te estas tomando unas buenas vacaciones, nos tieness acustambrados a leer publicaciones tuyas en espacios muy breves de tiempo, estoy con el mono de leer algo del 2012!! quiero una entrada nueva 🙁

Si, han sido unas buenas vacaciones! la próxima semana se publicarán las entradas finales sobre I2P y continuando con el tema del anonimato, comenzarán las publicaciones de FreeNet.
Espero que también te gusten!

Un Saludo y gracias por comentar.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *