Ya sabes qué es un certificado (05-01) y cómo TLS lo pone a trabajar en cada conexión (05-02). Pero en producción el certificado no es un concepto: es un artefacto operativo con fecha de caducidad, que alguien tiene que emitir, desplegar, vigilar, renovar y —el día malo— revocar. La mayoría de los incidentes "de TLS" del mundo real no son criptoanálisis: son certificados que caducaron un domingo porque nadie los renovó. En esta lección cierras el módulo con la parte que convierte todo lo anterior en un servicio fiable: el ciclo de vida completo, la automatización con ACME/Let's Encrypt y certbot, los mecanismos de revocación y sus miserias (CRL, OCSP, stapling), un monitor de caducidades en Python para los dominios de MediNube, el ciclo de vida de los certificados internos y de cliente mTLS de las clínicas, y un simulacro de incidente: la clave privada de portal.medinube.example filtrada en un repo público, con su runbook paso a paso.

Contenido

  1. El certificado como artefacto operativo: el ciclo de vida
  2. Caducidad: por qué las vidas cortas son una ventaja
  3. ACME y Let's Encrypt: emisión y renovación automáticas
  4. certbot para portal.medinube.example
  5. Revocación: cuándo y cómo (CRL, OCSP, stapling)
  6. Monitorización: enterarse antes de caducar
  7. Certificados internos: la mini-PKI y los clientes mTLS de las clínicas
  8. Pinning: mención y riesgos
  9. Incidente simulado: clave privada filtrada — el runbook

El certificado como artefacto operativo: el ciclo de vida

Un certificado nace, trabaja y muere. Todo lo que haremos en esta lección encaja en este ciclo:

flowchart LR
    E["Emitir<br/>(CSR → CA firma)"] --> D["Desplegar<br/>(nginx, recarga)"]
    D --> M["Monitorizar<br/>(¿caduca pronto?<br/>¿sigue desplegado el bueno?)"]
    M -->|"llega la ventana<br/>de renovación"| R["Renovar<br/>(nueva CSR, nuevo cert,<br/>idealmente nueva clave)"]
    R --> D
    M -->|"compromiso de clave<br/>o baja de la entidad"| X["Revocar<br/>(CRL/OCSP + reemitir)"]
    X --> E

Lee el diagrama como un bucle: la vida normal es emitir → desplegar → monitorizar → renovar → desplegar..., y la revocación es la salida de emergencia. Cada flecha es un punto de fallo clásico: emitir y no desplegar (el certificado nuevo se queda en una carpeta), desplegar y no recargar nginx (sirve el viejo hasta reiniciar), renovar sin monitorizar (no sabes si funcionó), revocar sin reemitir (servicio caído). La tesis de la lección: todo lo que pueda ser automático, debe serlo, y lo que no, necesita runbook escrito.

Caducidad: por qué las vidas cortas son una ventaja

Puede parecer un fastidio que los certificados caduquen, y encima cada vez antes: de los 3-5 años de la década de 2010 se pasó al máximo de 398 días aceptado por los navegadores (2020), Let's Encrypt emite a 90 días, y la industria (CA/Browser Forum) tiene aprobado el descenso escalonado hacia certificados de ~47 días en los próximos años. No es sadismo, es seguridad:

  • Limita la ventana de daño. Si una clave se compromete y nadie lo detecta, un certificado de 90 días deja de valer solo en semanas; uno de 3 años, en años. Como verás más abajo, la revocación funciona mal: la vida corta es la revocación que sí funciona.
  • Fuerza la automatización. Nadie renueva a mano cada 90 días sin fallar; con vidas cortas, o automatizas o sufres. Y un proceso automatizado y ejercitado cada 60 días es un proceso que sabes que funciona — comparado con el ritual anual que nadie recuerda cómo se hacía.
  • Acelera la criptoagilidad (regla nº 8). Si mañana hay que abandonar un algoritmo o cambiar de CA, un parque de certificados que rota cada 90 días se migra entero en semanas.
  • Mantiene los datos frescos. Dominios que cambian de dueño, empresas que desaparecen: la información validada por la CA caduca pronto.

Cambio de mentalidad para el equipo de MediNube: el certificado no es "algo que se instala", es algo que rota, como deberían rotar las claves (tema que el módulo 6 retoma para las claves de aplicación).

ACME y Let's Encrypt: emisión y renovación automáticas

Let's Encrypt (2015) cambió el juego: certificados DV gratuitos, emitidos en segundos, mediante un protocolo abierto de emisión automatizada: ACME (RFC 8555). La idea de ACME es automatizar justo lo que en 05-01 hacía "la CA por detrás": verificar que quien pide el certificado controla el dominio. Para ello propone retos (challenges):

HTTP-01 DNS-01
Qué exige demostrar Control del servidor web del dominio Control del DNS del dominio
Mecánica La CA pide http://tu-dominio/.well-known/acme-challenge/<token> y debe encontrar el valor acordado Publicas un registro TXT _acme-challenge.tu-dominio con el valor acordado
Requisitos Puerto 80 accesible desde Internet API de tu proveedor DNS (para automatizar)
¿Wildcard (*.medinube.example)? No — es la única vía
¿Sirve para máquinas no expuestas a Internet? No Sí (el reto se resuelve en el DNS, no en la máquina)
Complejidad Mínima Depende del proveedor DNS

El flujo completo, con las piezas que ya conoces: el cliente ACME genera una clave de cuenta (firmas, 04-03) → pide un certificado para portal.medinube.example → la CA propone el reto → el cliente lo resuelve (fichero HTTP o TXT en DNS) → la CA lo comprueba desde fuera → el cliente envía la CSR (05-01) → la CA emite y publica en los logs de Certificate Transparency (05-01) → el cliente instala el certificado. Cero humanos.

certbot para portal.medinube.example

certbot es el cliente ACME de referencia. Puesta en marcha para el portal, con el nginx de 05-02 ya sirviendo el dominio:

# Instalación en Debian/Ubuntu (existe también vía snap/pip):
sudo apt install certbot python3-certbot-nginx

# Emisión + configuración automática de nginx en un paso:
# --nginx: usa nginx para resolver el reto HTTP-01 Y edita la config
#          (rutas ssl_certificate*, redirección 80→443 si se pide).
# -d: el dominio (repetible: -d www.medinube.example ...).
sudo certbot --nginx -d portal.medinube.example \
    --email [email protected] --agree-tos

Tras esto, certbot deja el material en rutas estables que nginx referencia (¡fíjate: son las de 05-02!):

  • /etc/letsencrypt/live/portal.medinube.example/fullchain.pem — hoja + intermedia.
  • /etc/letsencrypt/live/portal.medinube.example/privkey.pem — la clave privada (permisos restrictivos; live/ son symlinks a la última versión, por eso nginx no necesita cambios al renovar).

La renovación automática es la mitad importante. Los paquetes modernos instalan un timer de systemd (o un cron) que ejecuta certbot renew dos veces al día; certbot solo renueva los certificados a los que les quedan menos de 30 días, y tras renovar recarga nginx (vía hook):

# ¿Está el timer activo?
systemctl list-timers | grep certbot

# Ensayo general SIN emitir nada real: verifica reto, permisos y hooks.
# Hazlo SIEMPRE tras cambiar la configuración de nginx o del DNS.
sudo certbot renew --dry-run

# Equivalente cron si no hay systemd (a las 03:17 y 15:17, minuto aleatorio
# elegido para no golpear a la CA en la hora en punto):
# 17 3,15 * * * root certbot renew --quiet --deploy-hook "systemctl reload nginx"

Para el comodín *.medinube.example (si MediNube diera un subdominio a cada clínica) el reto debe ser DNS-01, con un plugin del proveedor DNS (certbot-dns-cloudflare, certbot-dns-route53, etc.) y sus credenciales — credenciales que son un secreto más que proteger, anticipo directo de 06-01.

Con esto, la "monitorización" no desaparece: la renovación automática también falla (cambió el DNS, un firewall nuevo bloquea el puerto 80, caducó la credencial del plugin DNS). Automatiza la renovación y vigila el resultado (sección de monitorización).

Revocación: cuándo y cómo (CRL, OCSP, stapling)

Revocar = declarar inválido un certificado antes de su caducidad. Los dos disparadores que importan a MediNube:

  1. Compromiso de clave privada (el incidente del final de la lección): cualquiera con la clave puede suplantar al portal hasta que el certificado deje de valer.
  2. Baja de una entidad: el Centro Médico Luna rescinde su contrato — su certificado de cliente mTLS (05-02) debe dejar de dar acceso a la API hoy, no cuando caduque.

El problema: avisar a todos los clientes del mundo de que un certificado concreto ya no vale es intrínsecamente difícil. Los mecanismos, todos imperfectos:

Mecanismo Cómo funciona Problemas
CRL (Certificate Revocation List) La CA publica una lista firmada de números de serie revocados; el cliente la descarga y consulta Listas grandes, se cachean horas/días; ventana de exposición; muchos clientes ni la miran
OCSP El cliente pregunta a la CA en tiempo real por ese certificado Latencia extra; la CA ve tu navegación (privacidad); y si el OCSP no responde, casi todos los clientes hacen soft-fail: siguen adelante — un MITM que bloquee OCSP anula el mecanismo
OCSP stapling El servidor obtiene periódicamente una respuesta OCSP firmada y fresca y la "grapa" al handshake Arregla latencia y privacidad; pero si el servidor no grapa, el cliente suele tolerarlo (salvo Must-Staple, poco usado); Let's Encrypt de hecho retiró su servicio OCSP en 2025 para volver a CRLs
Vida corta No revocar: dejar que expire en días/semanas Es la revocación de facto del ecosistema: no depende de que el cliente compruebe nada; su "ventana" es la vida restante del certificado

Lectura honesta de la tabla: la revocación en la Web PKI pública es un mecanismo de mejor esfuerzo. Por eso la industria empuja vidas cada vez más cortas y por eso, tras un compromiso, el runbook (última sección) nunca se queda en "revocar": siempre es revocar + reemitir con clave nueva + desplegar, asumiendo que habrá clientes que no se enteren de la revocación. En cambio, en una PKI interna (nuestra mini-PKI), tú controlas ambos extremos: puedes distribuir CRLs de verdad y hacer que tus servidores las apliquen estrictamente — la revocación interna sí puede ser fiable.

Con la mini-PKI del laboratorio, revocar requiere llevar una pequeña base de datos de emisiones (openssl ca en lugar del openssl x509 -req directo de 05-01); el flujo conceptual es: marcar el serial como revocado → regenerar la CRL firmada (openssl ca -gencrl) → distribuirla a los servidores que validan (el nginx de mTLS la carga con ssl_crl).

Monitorización: enterarse antes de caducar

Regla de operación: ningún certificado de MediNube caduca por sorpresa. El monitor más simple y robusto no mira la configuración, mira lo que de verdad se está sirviendo — igual que haría un cliente. Script medinube/monitor_certs.py:

"""Monitor de caducidad de certificados de MediNube.

Se conecta a cada dominio como lo haría un cliente TLS real,
lee el certificado presentado y avisa si caduca en < UMBRAL_DIAS.
Pensado para cron diario; el aviso puede entregarse por el webhook
firmado con HMAC de la lección 03-02.
"""
import socket
import ssl
import sys
from datetime import datetime, timezone

from cryptography import x509

# Todo endpoint TLS de MediNube expuesto o interno importante:
DOMINIOS = [
    ("portal.medinube.example", 443),
    ("api.medinube.example", 443),
]
UMBRAL_DIAS = 30   # margen para reaccionar; > ventana de renovación de certbot


def dias_restantes(host: str, puerto: int) -> int:
    """Devuelve los días de validez que le quedan al certificado servido."""
    contexto = ssl.create_default_context()          # validación completa (05-02)
    with socket.create_connection((host, puerto), timeout=10) as tcp:
        with contexto.wrap_socket(tcp, server_hostname=host) as tls:
            # getpeercert(binary_form=True) → DER; lo parseamos con
            # cryptography para trabajar con objetos, como en 05-01.
            der = tls.getpeercert(binary_form=True)
    cert = x509.load_der_x509_certificate(der)
    restante = cert.not_valid_after_utc - datetime.now(timezone.utc)
    return restante.days


def main() -> int:
    codigo_salida = 0
    for host, puerto in DOMINIOS:
        try:
            dias = dias_restantes(host, puerto)
        except ssl.SSLCertVerificationError as e:
            # Un cert inválido YA (caducado, cadena rota) es alerta máxima:
            print(f"[CRITICO] {host}: certificado NO valida: {e}")
            codigo_salida = 2
            continue
        except OSError as e:
            print(f"[ERROR] {host}: no se pudo conectar: {e}")
            codigo_salida = max(codigo_salida, 1)
            continue
        if dias < UMBRAL_DIAS:
            print(f"[AVISO] {host}: caduca en {dias} días (< {UMBRAL_DIAS})")
            codigo_salida = max(codigo_salida, 1)
        else:
            print(f"[OK] {host}: {dias} días restantes")
    return codigo_salida


if __name__ == "__main__":
    sys.exit(main())

Puntos de diseño, en detalle:

  • Usamos ssl.create_default_context() con validación: si el certificado ya no valida, queremos la alerta crítica, no leerlo igualmente. (Para inspeccionar certificados ya caducados harían falta trucos sin validación; para monitorizar, la validación es parte del test.)
  • getpeercert(binary_form=True) devuelve el DER de la hoja; cryptography nos da not_valid_after_utc como datetime con zona, y restamos contra datetime.now(timezone.utc) — el mismo patrón del ejercicio 2 de 05-01.
  • El umbral de 30 días no es casual: certbot renueva a falta de <30 días, así que "quedan <30 días y sigue sin renovarse" significa la automatización está fallando y aún hay margen humano.
  • El código de salida (0/1/2) lo entiende cualquier programador de tareas y sistemas de monitorización. En cron: 0 8 * * * python3 /opt/medinube/monitor_certs.py || curl ... # notificar. Para notificar al canal del equipo, reutiliza el webhook firmado de 03-02: el aviso viaja con X-MediNube-Timestamp y X-MediNube-Firma: v1=<hex>, porque una alerta de seguridad falsificable sería irónica.
  • Extensión natural (ejercicio 1): vigilar también los certificados de cliente mTLS de las clínicas, que no se pueden consultar conectándose, sino leyendo los ficheros PEM emitidos por la CA interna.

Certificados internos: la mini-PKI y los clientes mTLS de las clínicas

Todo el ciclo de vida aplica igual —con matices— a los certificados que no vienen de una CA pública:

La CA raíz del laboratorio (05-01). También caduca (la emitimos a 5 años). Su renovación es un evento mayor: hay que redistribuir la nueva raíz a todos los clientes que confían en ella (verify=/etc/medinube/pki/ca.crt en las integraciones de 05-02) antes de que expire la vieja, con un periodo de solape en el que ambas son válidas. Apúntalo en el calendario del equipo el día que la crees: "renovar raíz interna: empezar 6 meses antes de 2031". Las hojas internas, en cambio, se rotan como las públicas: cortas y automatizadas (existen herramientas tipo ACME interno — step-ca, HashiCorp Vault — cuando la mini-PKI artesanal se queda pequeña; solo la mención, la gestión de secretos es 06-01).

Alta de una clínica (mTLS, 05-02). Procedimiento estándar: la clínica genera su clave y su CSR (la clave privada nunca viaja, 05-01) con SAN clinica-sol.clientes.medinube.example → MediNube verifica por canal fiable que la CSR es de quien dice (¡la validación de identidad es el trabajo de la CA, aunque la CA seas tú!) → la CA interna firma con vida corta (90 días) → la clínica instala y prueba contra el endpoint mTLS.

Baja de una clínica = revocación + defensa en profundidad. El Centro Médico Luna causa baja. Pasos: (1) revocar su certificado en la CA interna y regenerar/distribuir la CRL al nginx de mTLS; (2) además, desactivar su identidad ($ssl_client_s_dn) en la capa de autorización de la aplicación — porque la regla nº 9 sigue vigente: la criptografía no sustituye al control de acceso, y así la baja es efectiva aunque una CRL tarde en propagarse; (3) la vida corta de 90 días actúa de red de seguridad final. Tres capas, ninguna suficiente por sí sola, las tres juntas robustas.

Pinning: mención y riesgos

Oirás hablar de pinning: fijar en el cliente (típicamente una app móvil) la huella del certificado o de la clave pública del servidor, y rechazar cualquier otra aunque la cadena valide. Sube el listón contra CAs comprometidas... y es una trampa operativa: el pin vive en la app instalada, y el certificado rota cada 90 días. Si rotas la clave sin actualizar el pin (o sin haber "pinneado" también una clave de respaldo), tu propia app se niega a hablar contigo — un autodenegación de servicio en toda la base instalada. El HPKP de los navegadores se retiró por esta clase de accidentes; hoy el ecosistema prefiere Certificate Transparency (05-01) como control. Para MediNube: nada de pinning salvo caso muy justificado, y siempre con claves de respaldo y caducidad del pin. Sabed que existe, sabed por qué casi nunca compensa.

Incidente simulado: clave privada filtrada — el runbook

Viernes, 17:40. Un desarrollador de MediNube avisa: al hacer público un repositorio de utilidades ha descubierto que contiene, desde hace tres semanas, un directorio deploy/ con privkey.pem de portal.medinube.example. La clave privada del certificado del portal está en un repo público. ¿Pánico? No: runbook. (Nota previa tranquilizadora que debes a 05-02: gracias a la forward secrecy de TLS 1.3, el tráfico pasado capturado NO es descifrable con esa clave; el riesgo es de suplantación desde ahora.)

  1. Confirmar y acotar (minuto 0). ¿Es realmente la clave en uso? Compara la clave pública derivada del fichero filtrado con la del certificado servido — misma clave = mismo módulo/punto público:

    openssl pkey -in privkey_filtrada.pem -pubout | openssl sha256
    openssl s_client -connect portal.medinube.example:443 \\
        -servername portal.medinube.example </dev/null 2>/dev/null \\
      | openssl x509 -noout -pubkey | openssl sha256
    # ¿Coinciden los hashes? Entonces la clave activa está comprometida.
    
  2. Tratar la clave como comprometida desde ya. Tres semanas pública = copiada. Retirarla del repo no deshace nada (el historial de git y los clones ajenos existen); reescribir el historial es higiene, no remedio.

  3. Generar clave nueva + CSR + reemisión (minutos 5-15). Nunca reemitas con la misma clave. Con certbot es un solo paso que fuerza clave nueva y reemite:

    sudo certbot certonly --nginx -d portal.medinube.example --force-renewal
    # (certbot genera nueva privkey por defecto en cada emisión)
    
  4. Desplegar y recargar (minutos 15-20). sudo systemctl reload nginx y verificar con el comando del paso 1 que el portal ya sirve la clave nueva (los hashes ya no coinciden con la filtrada) y con s_client que Verify return code: 0 (ok).

  5. Revocar el certificado viejo (minutos 20-30). Ahora que el nuevo está sirviendo, invalida el antiguo — certbot permite declarar el motivo:

    sudo certbot revoke \\
        --cert-path /etc/letsencrypt/archive/portal.medinube.example/cert1.pem \\
        --reason keycompromise
    

    Recuerda la sección de revocación: habrá clientes que no se enteren; por eso los pasos 3-4 van antes — el servicio ya está a salvo aunque la revocación propague lenta. (Con Certificate Transparency, además, vigila que no aparezcan emisiones nuevas no solicitadas para tus dominios: si el atacante intentó algo más, se verá.)

  6. Caza colateral. ¿Qué más había en ese repo? ¿Otras claves, tokens, credenciales del plugin DNS de certbot? Cada hallazgo abre su propio mini-runbook de rotación.

  7. Post-mortem sin culpables (la semana siguiente). Causa raíz: una clave privada vivía en un directorio de trabajo normal, lista para ser commiteada. Acciones: escáner de secretos en CI (que bloquee el push), .gitignore defensivos, y la de fondo: las claves no viven en repos ni en directorios de proyecto; viven en un gestor de secretos. Que es, exactamente, donde empieza el módulo 6.

Ensaya este runbook en el laboratorio (ejercicio 3) antes de necesitarlo: un runbook no ensayado es una hipótesis.

Errores Comunes y Consejos

  • Confiar en que "certbot ya está puesto" sin vigilarlo. La renovación automática falla en silencio (firewall nuevo, DNS cambiado, credencial caducada). certbot renew --dry-run tras cada cambio de infraestructura + monitor externo de caducidad. Automatiza y verifica.
  • Renovar el certificado y no recargar el servicio. nginx carga el certificado al arrancar/recargar; si el hook de recarga falta, servirás el viejo hasta que caduque en la cara de los usuarios. El monitor lo detecta porque mira lo servido, no lo emitido.
  • Reemitir tras un compromiso reutilizando la clave privada. Reemitir con la misma clave comprometida es cambiar la funda del móvil robado. Clave nueva siempre.
  • Confiar la baja de una clínica solo a la revocación. CRL/OCSP propagan lento o no se consultan. Baja = revocar y desautorizar en la aplicación y dejar que la vida corta remate.
  • Wildcard "para simplificar" sin pensarlo. *.medinube.example es una sola clave que, comprometida, cubre todos los subdominios, y exige exponer credenciales DNS al automatismo. Úsalo cuando el caso lo pida, no por pereza de añadir -d.
  • Olvidar los certificados internos en el inventario. La raíz del laboratorio, los certs de clínica, el farmacias-ca.crt... también caducan. Todo certificado que exista debe estar en la lista de algún monitor, o caducará un domingo.
  • Datos sanitarios de por medio: recuerda que MediNube es un universo ficticio; en un despliegue real, un incidente como el del runbook implicaría además obligaciones RGPD (evaluación de la brecha y, en su caso, notificación a la autoridad) y revisión de compliance — el post-mortem técnico no es el final del papeleo.

Ejercicios

Ejercicio 1 — Monitor de ficheros PEM. Amplía el monitor con dias_restantes_pem(ruta: str) -> tuple[str, int] que, dado un PEM local (p. ej. el certificado de cliente mTLS de la Clínica Sol emitido por la CA interna), devuelva (nombre_del_subject, dias_restantes). Añade una lista FICHEROS_PEM al main() con el mismo umbral. Así el monitor cubre también lo que no se puede consultar conectándose.

Ejercicio 2 — Decisión de reto ACME. Para cada caso de MediNube, decide HTTP-01 o DNS-01 y justifica: (a) portal.medinube.example, servidor web público; (b) *.clientes.medinube.example, un subdominio por clínica; (c) intranet.medinube.example, resoluble públicamente en DNS pero solo accesible desde la VPN corporativa.

Ejercicio 3 — Simulacro de compromiso en el laboratorio. Con la mini-PKI de 05-01: (1) genera una clave y certificado nuevos para portal.medinube.example (portal2.key/portal2.crt) firmados por tu CA; (2) verifica con openssl que la clave pública de portal2.crt es distinta de la de portal.crt (el equivalente del paso 1 del runbook); (3) apunta qué paso del runbook real no puedes reproducir en el laboratorio tal como lo montaste, y por qué.

Soluciones

Solución 1.

def dias_restantes_pem(ruta: str) -> tuple[str, int]:
    with open(ruta, "rb") as f:
        cert = x509.load_pem_x509_certificate(f.read())
    restante = cert.not_valid_after_utc - datetime.now(timezone.utc)
    return cert.subject.rfc4514_string(), restante.days

FICHEROS_PEM = [
    "/etc/medinube/pki/ca.crt",                      # ¡la raíz también caduca!
    "/etc/medinube/pki/clientes/clinica-sol.crt",
]

# En main(), tras el bucle de DOMINIOS:
    for ruta in FICHEROS_PEM:
        try:
            subject, dias = dias_restantes_pem(ruta)
        except (OSError, ValueError) as e:
            print(f"[ERROR] {ruta}: no se pudo leer/parsear: {e}")
            codigo_salida = max(codigo_salida, 1)
            continue
        if dias < UMBRAL_DIAS:
            print(f"[AVISO] {subject} ({ruta}): caduca en {dias} días")
            codigo_salida = max(codigo_salida, 1)
        else:
            print(f"[OK] {subject}: {dias} días restantes")

Nota: para la CA raíz conviene un umbral mayor (p. ej. 180 días), porque su renovación implica redistribuir confianza a todos los clientes, no un simple reload — puedes parametrizar el umbral por fichero como mejora.

Solución 2. (a) HTTP-01: servidor público con el 80 accesible, el caso ideal, cero credenciales extra. (b) DNS-01 obligatorio: es un wildcard, y HTTP-01 no puede emitirlos; además exige automatizar contra la API del DNS y proteger esa credencial (06-01). (c) DNS-01: la CA no puede alcanzar el servidor por HTTP (solo VPN), pero el reto DNS se resuelve en el DNS público sin exponer la intranet. Alternativa razonable que puedes haber propuesto: emitirlo con la CA interna del laboratorio, ya que solo lo consumen clientes corporativos que ya confían en ella.

Solución 3.

cd ~/medinube-lab/pki
# (1) clave y certificado nuevos, misma identidad:
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out portal2.key
openssl req -new -key portal2.key \
    -subj "/CN=portal.medinube.example/O=MediNube SL" \
    -addext "subjectAltName=DNS:portal.medinube.example" -out portal2.csr
openssl x509 -req -in portal2.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
    -days 90 -sha256 -copy_extensions copy -out portal2.crt

# (2) comparar claves públicas por hash (deben DIFERIR):
openssl x509 -in portal.crt  -noout -pubkey | openssl sha256
openssl x509 -in portal2.crt -noout -pubkey | openssl sha256

(3) El paso irreproducible es la revocación efectiva: emitimos con openssl x509 -req directo, sin la base de datos de emisiones de openssl ca, así que no hay de dónde generar una CRL; y aunque la hubiera, ningún cliente la consulta aún. Es una ilustración perfecta de la lección: revocar solo sirve si existe infraestructura de revocación y los clientes la aplican — de lo contrario, tus únicas defensas reales son reemitir+desplegar rápido y la vida corta del certificado.

Conclusión

Con esta lección, el certificado deja de ser un concepto y pasa a ser un artefacto con vida: emitir → desplegar → monitorizar → renovar → revocar, con ACME/certbot automatizando el bucle feliz para portal.medinube.example, vidas cortas como la revocación que de verdad funciona, CRL/OCSP/stapling como el recordatorio de que avisar al mundo entero es difícil, un monitor en Python que promete que nada caduca por sorpresa (avisando por el webhook firmado de 03-02), el ciclo completo de los certificados internos y de cliente mTLS (con la baja de una clínica resuelta en tres capas), y un runbook de compromiso de clave ensayado antes de necesitarlo.

Y con ella se cierra el Módulo 5 entero. Haz balance de la deuda que arrastrábamos desde 04-01: "saber de quién es cada clave pública, con respaldo verificable". Resuelta, y por triplicado: los certificados X.509 y las CAs (05-01) atan clave e identidad con firmas que MalloryClinic no puede falsificar; TLS (05-02) pone esa confianza a trabajar en cada conexión, ensamblando todas las primitivas del curso en un handshake; y la operación del ciclo de vida (esta lección) garantiza que esa confianza no se pudra con el tiempo. La grieta está cerrada: cifras, firmas, verificas, acuerdas claves y sabes con quién.

¿Entonces hemos terminado? Casi: queda llevarlo todo al día a día del desarrollo, que es donde la criptografía impecable se estropea por detalles mundanos. El incidente de esta lección lo ha insinuado: la clave no cayó por matemáticas, cayó por un git push. El Módulo 6: Criptografía en el Desarrollo Real empieza exactamente ahí — gestión de claves y secretos (06-01: dónde deberían haber vivido ese privkey.pem y las credenciales DNS de certbot), y sigue con el cifrado en reposo y en tránsito aplicado a la arquitectura de MediNube (06-02, incluido el envelope encryption que anticipamos en 04-05), los tokens firmados y JWT para las sesiones del portal (06-03), la galería de errores criptográficos comunes con todos los _MAL del curso reunidos (06-04) y la mirada a lo que viene: criptografía post-cuántica (06-05). Ya tienes todas las piezas y toda la confianza; ahora toca usarlas bien cuando nadie mira. Nos vemos en el módulo 6.

© Copyright 2026. Todos los derechos reservados