Construir tu propio servidor web siempre ha sido un reto para los entusiastas de la infraestructura, pero cuando le añades una capa de seguridad propia, la complejidad aumenta considerablemente. En este artículo analizamos la reciente publicación de Adding Homemade TLS to a Homemade Web Server, desglosando los conceptos clave y ofreciendo ejemplos prácticos para que puedas replicar el proceso.

Visión general del proyecto

Añadiendo TLS Casero a un Servidor Web Casero software development concept illustration, cover image, blog header, no te - imagen ilustrativa
Foto por Jackson Sophat en Unsplash

El autor parte de un servidor HTTP mínimo escrito en C y le incorpora una pila TLS propia, sin depender de bibliotecas como OpenSSL. El objetivo es educativo: comprender los componentes de la negociación segura, el cifrado de datos y la verificación de la cadena de confianza.

Implementación del TLS desde cero

Integración con el servidor HTTP adiendo TLS Casero - imagen ilustrativa
Foto por Bernd 📷 Dittrich en Unsplash

Para crear un protocolo TLS casero se necesita:

  • Un generador de números aleatorios criptográficamente seguro.
  • Implementaciones de AEAD (por ejemplo, AES‑GCM).
  • Gestión de certificados X.509 y firmas digitales.
  • Un estado de handshake que siga la especificación TLS 1.3.

El autor reutiliza la estructura de datos de OpenSSL para la parsificación de certificados, pero reescribe la lógica de handshake. A continuación, se muestra una extracción del código que genera el mensaje ClientHello:

/* Generación de ClientHello */
static int build_client_hello(tls_ctx *ctx, unsigned char *out, size_t out_len) {
    unsigned char *p = out;
    /* Versión TLS 1.3 */
    *p++ = 0x03; *p++ = 0x03; // legacy_version
    /* Random (32 bytes) */
    RAND_bytes(p, 32);
    p += 32;
    /* Session ID vacío */
    *p++ = 0x00;
    /* Cipher Suites */
    *p++ = 0x00; *p++ = 0x02; // length = 2
    *p++ = 0x13; *p++ = 0x01; // TLS_AES_128_GCM_SHA256
    /* Compression Methods */
    *p++ = 0x01; *p++ = 0x00; // null compression
    /* Extensions (simplificado) */
    // ...
    return p - out; // número de bytes escritos
}

Este fragmento ilustra cómo se construye el protocolo de manera manual, respetando el formato binario definido por la especificación.

Integración con el servidor HTTP

Una vez que el handshake está listo, el servidor debe decidir cuándo leer y escribir datos cifrados. La arquitectura propuesta utiliza un bucle de eventos sencillo:

while (1) {
    fd_set read_fds;
    FD_ZERO(&read_fds);
    FD_SET(sock, &read_fds);
    int ret = select(sock + 1, &read_fds, NULL, NULL, NULL);
    if (ret > 0 && FD_ISSET(sock, &read_fds)) {
        unsigned char buf[4096];
        int len = recv(sock, buf, sizeof(buf), 0);
        if (len <= 0) break; // desconexión
        // Desencriptar con la capa TLS
        int plain_len = tls_decrypt(ctx, buf, len, plain_buf);
        // Procesar petición HTTP
        handle_http_request(plain_buf, plain_len, sock);
    }
}

El código anterior muestra cómo el servidor delega la criptografía a la capa TLS antes de pasar los datos al parser HTTP.

Consideraciones de rendimiento y seguridad

Implementar TLS a mano es una excelente oportunidad de aprendizaje, pero no debe usarse en entornos de producción. Los principales riesgos son:

  • Errores de timing que pueden revelar información de clave.
  • Gestión incorrecta de certificados que permite ataques MITM.
  • Falta de optimizaciones como session resumption o HSTS.

Para un proyecto real, la recomendación es migrar a bibliotecas probadas como OpenSSL o LibreSSL.

Conclusión

La serie de Dmytro demuestra que construir TLS desde cero es factible y educativo, pero también subraya la complejidad inherente al protocolo. Al seguir este tutorial, los lectores obtienen una visión detallada de cada paso del handshake y de cómo acoplarlo a una aplicación de servidor web propia.

Referencias