Sockets SSL en Python

Recientemente un lector del blog me contactó para pedirme, muy amablemente, si podía ayudarle a comprender los conceptos de implementación de sockets SSL en Python. ¡Por supuesto! Y como siempre, fiel a la humilde ideología de este blog, quedará aquí todo expuesto con el fin de compartirlo con todos vosotros e intentar ayudar a alguien más.

En primer lugar, vamos a ver un breve resumen del concepto de socket.

Dentro del ámbito de la programación, se utiliza el término socket para hacer referencia, principalmente, a las siguientes definiciones:

  • Socket (def. [1]): concepto abstracto por el cual dos programas, normalmente situados en computadoras diferentes, pueden intercambiar un flujo de datos a través de un canal de comunicación.
  • Sockets (def. [2]): en muchos casos la Network Application Programming Interface (NAPI) denominada BSD Berkeley Sockets se abrevia a Sockets. Se trata de una interfaz de trabajo que se utiliza para relacionar la capa de aplicación con la capa de transporte. Normalmente, este tipo de interfaces las proporciona el sistema operativo. A continuación, siguen otras NAPIs conocidas:
    • TLI, XTI (de AT&T UNIX System V)
    • Winsock (de Microsoft)
    • MacTCP (de Apple Computer)

Veamos una imagen representativa de estas:

NAPI en el Modelo TCP-IP

La Network Application Programming Interface denominada BSD Berkeley Sockets se ha convertido en un estándar para la utilización de sockets en la red. Se trata de una NAPI basada en librerías que permite escribir programas en C con el fin de implementar las rutinas de intercomunicación entre procesos. En realidad, se compone por un conjunto de funciones, tales como socket(), bind(), listen() o connect(), donde cada una realiza una tarea específica para establecer, mantener o cerrar el canal de comunicación.

De alguna forma, esta Network API nos proporciona la interacción con unos ficheros (creados de una forma especial) que representan los extremos de la comunicación (endpoints) y que podemos, en Linux, leer o escribir con las clásicas funciones write() y read(). No obstante, también tenemos a nuestra disposición un conjunto de funciones especialmente diseñadas para realizar este tipo de tareas, como son las funciones send() y recv() o sendto() y recvfrom(). A continuación se encuentra una imagen que pretende representar gráficamente el concepto descrito:

Aplicación - Socket - Paquete - Socket - Aplicación

Ahora bien, existen dos tipos de sockets, aquellos que son orientados a la conexión y aquellos que no son orientados a la conexión. Los sockets orientados a la conexión garantizan que todos los datos llegarán de un programa a otro correctamente y se utilizan cuando la información a transmitir es importante y no se puede permitir perder ningún dato. Este tipo de sockets se basan en la utilización del protocolo TCP.

Por otro lado, tenemos los sockets no orientados a la conexión, que se utilizan cuando sólo se quiere garantizar que los datos a recibir sean correctos pero sin la necesidad de confirmar la llegada de todos estos. UDP es el protocolo de transporte utilizado en este caso.

En referencia a la cuestión práctica que se quiere dar solvencia, tenemos que plantearnos la siguiente pregunta: ¿Cómo puedo implementar un socket SSL en Python manteniendo una arquitectura cliente/servidor?

Como siempre, en Python está todo hecho. Existe una gran cantidad de módulos que ofrecen una alta abstracción a las implementaciones más bajas, permitiendo reducir la complejidad de las operaciones con un simple import y poco más. En este caso, para el tratamiento de sockets podemos contar con el módulo socket, el cual nos ofrece una abstracción de la interfaz de BSD Berkeley Sockets, y también con el módulo ssl, el cual nos facilita el cifrado SSL/TLS para los sockets mediante el uso de la librería OpenSSL.

Primeramente, lo que vamos a hacer es generar de forma simple el certificado que utilizará nuestro servidor:

1. Generamos la clave privada:

openssl genrsa -des3 -out server.key 1024

2. Generamos una solicitud de firma de certificado (CSR), donde especificaremos todos los atributos de identificación:

openssl req -new -key server.key -out server.csr

3. Eliminamos la passphrase de la clave para simplificar nuestro programa:

cp server.key server.key.org

openssl rsa -in server.key.org -out server.key

rm server.key.org

4. Generamos el certificado autofirmado:

openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

A partir de aquí, sólo nos queda por ver la implementación del código, la cual está disponible en este enlace y es muy simple. Sigue, a continuación, una imagen que corrobora el establecimiento de la conexión cifrada que se realiza con el código:

wireshark_tlsv1

Cabe destacar que para un uso más avanzado son interesantes los módulos de SocketServer y SimpleHTTPServer.

De forma adicional, también he subido otro programa en que se ofrece una shell de comandos del servidor al cliente. Como podréis ver, éste se encuentra escrito en C y por lo tanto se interactúa directamente con la interfaz de BSD Berkeley Sockets.

Por cierto, buena gente la de Cádiz 🙂

¡Saludos!

Ferran Verdés