Al final del módulo 4 dejamos una frase incómoda flotando en el aire: toda la matemática es impecable, pero descansa sobre una suposición sin verificar — que la clave pública que tienes es de quien crees. MalloryClinic no necesita romper RSA ni Curve25519: le basta con colarte su clave pública haciéndose pasar por la Clínica Sol. En esta lección construimos por fin la pieza que falta: el certificado digital X.509, un documento que ata una clave pública a una identidad con la firma de un tercero de confianza. Aprenderás a leer certificados reales con openssl y con Python, entenderás la cadena de confianza que va de tu navegador hasta una autoridad raíz, generarás una CSR para portal.medinube.example y montarás una mini-PKI de laboratorio. Al terminar, el ataque de MalloryClinic de la lección 04-04 dejará de funcionar — y sabrás exactamente por qué.
Contenido
- La grieta de la confianza: qué problema resuelve un certificado
- Anatomía de un certificado X.509
- Inspeccionar un certificado real con
openssly con Python - La cadena de confianza: raíz, intermedia y hoja
- Autoridades de certificación: por qué funciona el modelo (y cuándo ha fallado)
- La CSR: pedir un certificado para portal.medinube.example
- Tipos de validación: DV, OV y EV
- Mini-PKI de laboratorio para MediNube
- Cómo esto derrota a MalloryClinic
La grieta de la confianza: qué problema resuelve un certificado
Recapitulemos el problema con precisión. En 04-04, la Clínica Sol quería establecer un canal cifrado con MediNube usando X25519. MalloryClinic se colocó en medio e interceptó el intercambio de claves públicas:
- A la clínica le entregó su clave pública diciendo "soy MediNube".
- A MediNube le entregó otra clave suya diciendo "soy la Clínica Sol".
Ninguna primitiva del curso detecta esto, porque una clave pública, por sí sola, es solo un número. No dice de quién es. Las firmas de 04-03 tampoco ayudan por sí solas: verificar la firma de la Dra. Ferrer requiere tener su clave pública auténtica... que es exactamente el mismo problema, un escalón más arriba.
La solución no es una primitiva nueva. Es un documento:
Certificado digital = clave pública + identidad + firma de un tercero de confianza.
Es decir: alguien en quien ambas partes ya confían (la Autoridad de Certificación, CA) examina la evidencia de que una clave pública pertenece a una identidad ("este dominio", "esta empresa") y lo deja por escrito, firmado con su propia clave privada, usando exactamente las firmas digitales que ya dominas de la lección 04-03. Quien reciba el certificado puede verificar esa firma y, si confía en la CA, confiar en la asociación clave↔identidad.
Fíjate en que no hemos inventado criptografía nueva (regla de oro nº 1): un certificado X.509 es una estructura de datos firmada. La novedad es organizativa: quién firma, qué verifica antes de firmar y cómo se distribuye la confianza.
Anatomía de un certificado X.509
X.509 es el formato estándar de certificados (versión 3, definido en RFC 5280). Sus campos esenciales:
| Campo | Qué contiene | Ejemplo para MediNube |
|---|---|---|
| Subject | La identidad del titular (quién es el dueño de la clave) | CN=portal.medinube.example |
| Subject Public Key Info | La clave pública del titular y su algoritmo | Clave ECDSA P-256 o RSA 3072 |
| Issuer | Quién emitió (firmó) el certificado | CN=Lab CA de MediNube |
| Validity (Not Before / Not After) | Ventana temporal de validez | 2026-07-07 → 2026-10-05 |
| Serial Number | Identificador único del certificado dentro de la CA | 0x5A3F... |
| Subject Alternative Name (SAN) | Lista de nombres DNS/IP que el certificado cubre | DNS:portal.medinube.example |
| Key Usage / Extended Key Usage | Para qué está autorizada la clave | digitalSignature, serverAuth |
| Basic Constraints | Si el certificado puede a su vez firmar otros (CA:TRUE/FALSE) |
CA:FALSE en una hoja |
| Signature | La firma de la CA sobre todo lo anterior | ECDSA o RSA-PSS/PKCS#1 |
Y un dato derivado que usarás constantemente:
- Huella (fingerprint): el hash SHA-256 del certificado completo codificado en DER. No forma parte del certificado; se calcula. Sirve para identificarlo sin ambigüedad ("¿es este el certificado que desplegamos?"), igual que usabas hashes en el módulo 3 para verificar integridad.
Por qué hoy manda el SAN y no el CN
Históricamente, la identidad del servidor se ponía en el CN (Common Name) del subject: CN=portal.medinube.example. Ese diseño tenía dos problemas: el CN solo admite un nombre, y su semántica es ambigua (es un campo genérico de nombre, no específicamente un dominio). La extensión SAN lo resuelve: una lista explícita de nombres (DNS:..., IP:...), con tantas entradas como haga falta.
Desde 2017-2018, los navegadores y librerías modernas ignoran el CN por completo al validar el nombre del servidor: si el dominio no está en el SAN, el certificado no vale, aunque el CN coincida. Regla práctica para MediNube:
- El SAN es obligatorio y es lo único que se comprueba para el nombre.
- El CN es decorativo; se rellena por costumbre, pero no confíes en él ni valides contra él.
Inspeccionar un certificado real con openssl y con Python
La mejor forma de interiorizar la anatomía es abrir un certificado de verdad. Descarguemos el de un sitio real y examinémoslo (puedes usar cualquier dominio público; aquí, uno de ejemplo):
# Paso 1: conectar por TLS y guardar el certificado que presenta el servidor.
# -connect indica host:puerto; -servername envía el SNI (qué dominio pedimos).
# </dev/null cierra la entrada para que s_client no se quede esperando.
openssl s_client -connect www.wikipedia.org:443 \
-servername www.wikipedia.org </dev/null 2>/dev/null \
| openssl x509 -out /tmp/wikipedia.pem
# Paso 2: volcar el certificado en formato legible.
openssl x509 -in /tmp/wikipedia.pem -noout -textDesglose de la segunda orden, que usarás cientos de veces en tu carrera:
x509: subcomando de openssl para trabajar con certificados.-in /tmp/wikipedia.pem: fichero de entrada, en formato PEM (el Base64 entre-----BEGIN CERTIFICATE-----y-----END CERTIFICATE-----que ya conociste en 04-01; el binario puro se llama DER).-noout: no reimprimas el bloque PEM.-text: muestra todos los campos decodificados.
En la salida localiza: Issuer:, Validity, Subject:, X509v3 Subject Alternative Name: (¡ahí están los dominios!), X509v3 Key Usage, X509v3 Extended Key Usage: TLS Web Server Authentication y al final Signature Algorithm. Algunas consultas rápidas sin leer todo el volcado:
openssl x509 -in /tmp/wikipedia.pem -noout -subject -issuer -dates openssl x509 -in /tmp/wikipedia.pem -noout -ext subjectAltName openssl x509 -in /tmp/wikipedia.pem -noout -fingerprint -sha256
Ahora lo mismo desde Python con cryptography.x509, la cara programática que usará MediNube en sus comprobaciones automáticas:
from cryptography import x509
from cryptography.hazmat.primitives import hashes
# Cargamos el mismo PEM que descargamos con openssl.
with open("/tmp/wikipedia.pem", "rb") as f:
cert = x509.load_pem_x509_certificate(f.read())
print("Subject:", cert.subject.rfc4514_string())
print("Issuer: ", cert.issuer.rfc4514_string())
# not_valid_before_utc / not_valid_after_utc devuelven datetimes con zona UTC
# (las variantes sin _utc están obsoletas: devolvían datetimes "naive").
print("Válido desde:", cert.not_valid_before_utc)
print("Válido hasta:", cert.not_valid_after_utc)
# El SAN es una extensión: hay que pedirla explícitamente.
san = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
print("SAN:", san.value.get_values_for_type(x509.DNSName))
# Huella SHA-256 del certificado (sobre su codificación DER).
print("Huella SHA-256:", cert.fingerprint(hashes.SHA256()).hex())Puntos a entender del código:
load_pem_x509_certificateparsea el PEM a un objetoCertificateinmutable.- Las extensiones (SAN, Key Usage...) se consultan con
get_extension_for_class; si no existe, lanzaExtensionNotFound— en un certificado de servidor moderno el SAN debe existir. fingerprint(hashes.SHA256())es literalmentesha256(DER): el módulo 3 trabajando para el módulo 5.
La cadena de confianza: raíz, intermedia y hoja
¿Y quién firma el certificado de la CA? En la práctica, los certificados forman una cadena:
graph TD
R["CA Raíz<br/>(autofirmada, clave offline,<br/>vive en el trust store del SO/navegador)"]
I["CA Intermedia<br/>(firmada por la raíz,<br/>emite certificados a diario)"]
H["Certificado hoja<br/>portal.medinube.example<br/>(firmado por la intermedia, CA:FALSE)"]
R -- "firma" --> I
I -- "firma" --> H
- CA raíz: certificado autofirmado (issuer = subject). Su clave privada es tan valiosa que se custodia offline, en HSMs, y se usa poquísimas veces: solo para firmar intermedias. Tu confianza en ella no viene de ninguna firma, sino de que está preinstalada en el trust store de tu sistema operativo o navegador (en Debian/Ubuntu:
/etc/ssl/certs/, gestionado por el paqueteca-certificates; Firefox y Java llevan el suyo propio). - CA intermedia: firmada por la raíz. Es la que trabaja a diario emitiendo certificados. Si su clave se compromete, se revoca la intermedia sin tocar la raíz — es un cortafuegos organizativo, y otra aplicación del principio de criptoagilidad y compartimentación.
- Certificado hoja (leaf / end-entity): el del servidor, con
CA:FALSE. No puede firmar otros certificados.
Cómo se verifica la cadena
Cuando un cliente (el navegador de Ana Pérez, o el requests de la Clínica Sol) recibe el certificado de portal.medinube.example, hace, entre otras, estas comprobaciones:
- Construir la cadena: el servidor envía la hoja y las intermedias; el cliente enlaza cada certificado con su emisor (el
issuerde uno debe ser elsubjectdel siguiente) hasta llegar a una raíz que ya está en su trust store. - Verificar cada firma: con la clave pública del emisor se verifica la firma del certificado hijo — exactamente
verificar_firma()de 04-03, aplicada eslabón a eslabón. - Validez temporal: ahora ∈ [Not Before, Not After] en todos los eslabones.
- Restricciones: las intermedias tienen
CA:TRUE; la hoja tiene los Key Usage adecuados (serverAuth). - El nombre: el dominio solicitado aparece en el SAN de la hoja.
- Revocación: ¿ha sido revocado alguno? (Los mecanismos — CRL, OCSP — los veremos en 05-03; por ahora quédate con que los certificados también caducan y se revocan.)
Si todo pasa, y solo entonces, el cliente acepta que la clave pública de la hoja pertenece a portal.medinube.example. La confianza fluye de la raíz hacia abajo mediante firmas verificables: eso es una PKI (Public Key Infrastructure).
Un matiz práctico: en la Web PKI las hojas usan hoy claves ECDSA P-256/P-384 o RSA. Ed25519 —la elección de MediNube para las recetas en 04-03— es excelente, pero los navegadores aún no lo aceptan en certificados TLS públicos. Cada contexto tiene su ecosistema: recetas internas con Ed25519, certificados web con ECDSA/RSA. Criptoagilidad, regla nº 8.
Autoridades de certificación: por qué funciona el modelo (y cuándo ha fallado)
Una CA es una organización cuyo negocio es verificar identidades y firmar certificados, sometida a auditorías (los Baseline Requirements del CA/Browser Forum) bajo amenaza de expulsión de los trust stores. El modelo funciona porque:
- Escala: no necesitas intercambiar claves en persona con cada web del planeta; basta confiar en ~100-150 raíces preinstaladas.
- La confianza es revocable: si una CA lo hace mal, los navegadores la expulsan y todos sus certificados dejan de valer de golpe.
- Incentivos alineados: la CA vive de su reputación.
Pero es un modelo con un defecto estructural: cualquier CA de tu trust store puede emitir un certificado para cualquier dominio. La seguridad la marca la peor CA de la lista, no la mejor.
Caso histórico: DigiNotar (2011). Esta CA holandesa fue comprometida y los atacantes emitieron más de 500 certificados fraudulentos, incluido uno para *.google.com, usado para interceptar el correo de miles de usuarios en Irán — un MalloryClinic a escala estatal, con certificados técnicamente válidos. Cuando se descubrió, los navegadores expulsaron a DigiNotar de sus trust stores y la empresa quebró en semanas. Moraleja doble: el sistema de castigo funciona, pero el daño ya estaba hecho.
La respuesta: Certificate Transparency (CT). Desde 2018, los navegadores exigen que todo certificado público esté inscrito en registros públicos append-only (auditables, imposibles de reescribir discretamente). Cualquiera puede monitorizarlos: si mañana una CA emitiera un certificado para portal.medinube.example que MediNube no ha pedido, el equipo lo vería en los logs de CT y saltarían las alarmas. Quédate con el concepto: CT no impide la mala emisión, la hace detectable. (Puedes explorar los logs en buscadores públicos como crt.sh.)
La CSR: pedir un certificado para portal.medinube.example
¿Cómo pide MediNube un certificado a una CA? Con una CSR (Certificate Signing Request): un fichero que contiene la clave pública, la identidad solicitada (SAN incluido) y una autofirma que demuestra que quien pide posee la clave privada correspondiente. Fundamental: la clave privada nunca viaja a la CA; se genera y se queda en los servidores de MediNube.
Con openssl, en dos órdenes (o una sola con -newkey):
# 1) Generar la clave privada del servidor (ECDSA P-256, estándar actual en TLS).
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 \
-out portal.key
chmod 600 portal.key # solo el propietario puede leerla
# 2) Generar la CSR: identidad + clave pública + autofirma.
# -subj evita el cuestionario interactivo; -addext añade el SAN (lo que de
# verdad importa, como vimos: el CN es decorativo).
openssl req -new -key portal.key \
-subj "/CN=portal.medinube.example/O=MediNube SL" \
-addext "subjectAltName=DNS:portal.medinube.example" \
-out portal.csr
# 3) Revisar lo que vamos a enviar (¡el SAN debe estar!).
openssl req -in portal.csr -noout -textLo mismo desde Python, útil para automatizar altas de clínicas en el futuro:
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.x509.oid import NameOID
# 1) Clave privada ECDSA P-256 — se queda en MediNube, jamás sale.
clave_privada = ec.generate_private_key(ec.SECP256R1())
# 2) Construir la CSR con el patrón "builder": cada llamada devuelve un
# builder nuevo con ese dato añadido, y sign() la firma con nuestra clave
# (la autofirma que demuestra posesión de la privada).
csr = (
x509.CertificateSigningRequestBuilder()
.subject_name(x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, "portal.medinube.example"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "MediNube SL"),
]))
.add_extension(
x509.SubjectAlternativeName([x509.DNSName("portal.medinube.example")]),
critical=False,
)
.sign(clave_privada, hashes.SHA256())
)
# 3) Serializar a PEM para enviarla a la CA.
with open("portal.csr", "wb") as f:
f.write(csr.public_bytes(serialization.Encoding.PEM))
with open("portal.key", "wb") as f:
f.write(clave_privada.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.PKCS8,
# En 04-01 aprendiste BestAvailableEncryption para proteger claves
# en disco; para un servidor que debe arrancar sin intervención
# humana se suele guardar sin cifrar PERO con permisos 600 y, mejor,
# en un gestor de secretos (lo veremos en 06-01).
serialization.NoEncryption(),
))La CA recibe la CSR, valida que controlas la identidad que pides (ahora veremos cómo) y, si todo cuadra, te devuelve el certificado firmado.
Tipos de validación: DV, OV y EV
¿Qué comprueba exactamente la CA antes de firmar? Depende del tipo de certificado:
| Tipo | Qué valida la CA | Cómo lo valida | Emisión | Qué ve el usuario | Uso típico |
|---|---|---|---|---|---|
| DV (Domain Validation) | Que controlas el dominio | Reto técnico automatizado (fichero HTTP, registro DNS, correo) | Minutos, automatizable (Let's Encrypt) | Candado; solo el dominio | La inmensa mayoría de la web; portal.medinube.example |
| OV (Organization Validation) | Dominio + que la organización existe | DV + registros mercantiles, verificación telefónica | Días | El nombre de la empresa en los detalles del certificado | Empresas que quieren identidad corporativa visible |
| EV (Extended Validation) | Dominio + verificación exhaustiva de la entidad legal | Proceso manual estricto | Días/semanas | Antes, barra verde con el nombre; hoy los navegadores ya no la muestran | Banca y sectores regulados, en retroceso |
Detalle clave: los tres cifran exactamente igual. La criptografía de un DV gratuito y de un EV carísimo es la misma; cambia cuánta identidad humana hay verificada detrás. Para el portal de MediNube, un DV automatizado es la elección moderna (y en 05-03 verás cómo automatizar su emisión y renovación con ACME).
Mini-PKI de laboratorio para MediNube
Vamos a construir nuestra propia CA y a firmar un certificado, para tocar con las manos todo lo anterior.
Solo para laboratorio. Esta CA sirve para el entorno de desarrollo interno de MediNube y para aprender. Nunca uses una CA casera para servicios públicos, y nunca instales raíces de terceros en tu trust store a la ligera: quien controla una raíz de tu trust store puede suplantar cualquier web para ti. En producción pública, siempre una CA real (05-03). Y recuerda: MediNube maneja datos sanitarios ficticios en este curso; un despliegue real exigiría además revisión de seguridad y cumplimiento RGPD.
mkdir -p ~/medinube-lab/pki && cd ~/medinube-lab/pki
# ── 1. La CA raíz del laboratorio ──────────────────────────────────────
# Clave privada de la raíz (esta es LA joya: en una PKI real viviría offline).
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out ca.key
chmod 600 ca.key
# Certificado raíz AUTOFIRMADO (-x509 emite certificado en vez de CSR).
# basicConstraints CA:TRUE marcado critical: esto ES una CA.
# keyUsage keyCertSign,cRLSign: su clave solo firma certificados y CRLs.
openssl req -x509 -new -key ca.key -sha384 -days 1825 \
-subj "/CN=MediNube Lab Root CA/O=MediNube SL" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-out ca.crt
# ── 2. La hoja: clave + CSR del portal (igual que en la sección anterior) ──
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out portal.key
chmod 600 portal.key
openssl req -new -key portal.key \
-subj "/CN=portal.medinube.example/O=MediNube SL" \
-addext "subjectAltName=DNS:portal.medinube.example" \
-out portal.csr
# ── 3. La CA firma la CSR y emite el certificado hoja ──────────────────
# -CA/-CAkey: quién firma. -copy_extensions copy: conserva el SAN de la CSR.
# 90 días: vida corta a propósito (en 05-03 verás por qué es buena idea).
openssl x509 -req -in portal.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-days 90 -sha256 -copy_extensions copy \
-out portal.crt
# ── 4. Verificar la cadena: ¿valida portal.crt contra nuestra raíz? ────
openssl verify -CAfile ca.crt portal.crt
# → portal.crt: OKEse openssl verify final ejecuta en pequeño lo que hace tu navegador: construye la cadena portal.crt → ca.crt y verifica firma, fechas y restricciones. Comprueba el resultado también con lo aprendido: openssl x509 -in portal.crt -noout -subject -issuer -ext subjectAltName — verás que el issuer ya no es el propio subject, sino tu CA de laboratorio. (Hemos omitido la intermedia para simplificar; el principio con tres eslabones es idéntico.)
Cómo esto derrota a MalloryClinic
Volvamos al diagrama del ataque de 04-04, ahora con certificados en juego:
sequenceDiagram
participant C as Clínica Sol
participant M as MalloryClinic (MITM)
participant S as portal.medinube.example
C->>M: Hola, quiero hablar con portal.medinube.example
M->>C: Toma "mi" certificado (clave de Mallory)
Note over C: Verificación de cadena:<br/>1. ¿Firmado por una CA de mi trust store? ✗<br/>(o, si Mallory usa un cert robado ajeno:<br/>2. ¿portal.medinube.example en el SAN? ✗)
C--xM: ABORTAR conexión. Certificado inválido.
MalloryClinic tiene exactamente tres opciones, todas malas:
- Presentar su propia clave con un certificado autofirmado o de su CA casera → la cadena no termina en ninguna raíz del trust store de la clínica. Rechazado.
- Presentar un certificado válido de otro dominio (uno que haya conseguido legítimamente) → la firma de la cadena valida, pero
portal.medinube.exampleno está en su SAN. Rechazado. - Conseguir que una CA real le emita un certificado para
portal.medinube.example→ tendría que superar la validación de control del dominio sin controlarlo y, aun si una CA fallara (DigiNotar), Certificate Transparency dejaría el certificado fraudulento a la vista de los monitores de MediNube.
La clave privada de la CA firma la asociación identidad↔clave antes de que Mallory pueda intervenir, y esa firma viaja con el certificado. La grieta que arrastramos desde 04-01 está, por fin, técnicamente cerrada. Nótese el precio: hemos añadido un tercero de confianza y todo un aparato operativo alrededor — certificados que caducan, se renuevan y se revocan (05-03).
Errores Comunes y Consejos
- Validar contra el CN en vez del SAN. Código heredado que compara el CN "a mano" acepta certificados que ningún navegador aceptaría. Deja la validación de nombre a la librería (lo verás en 05-02); si algún día la haces tú, SAN y solo SAN.
- Confundir certificado y clave privada. El
.crtes público, se regala; el.keyes el secreto. Enviar a alguien "el certificado" jamás incluye la clave. Si una CA te pide tu clave privada, huye. - Enviar la clave privada con la CSR. La CSR ya contiene la clave pública y la prueba de posesión; la privada no sale del servidor. Nunca.
- Deshabilitar la verificación "porque falla". Un error de validación de certificado es el sistema funcionando. La tentación de silenciarlo (el
verify=Falseque encontrarás en código heredado de MediNube en 05-02) equivale a volver al mundo de 04-04 donde Mallory campaba a sus anchas. - Instalar raíces alegremente en el trust store. Cada raíz que añades puede suplantar cualquier sitio para ti. La raíz de laboratorio de esta lección: solo en entornos de desarrollo controlados.
- Ignorar
Basic ConstraintsyKey Usageal emitir. Una hoja emitida por descuido conCA:TRUEpuede firmar certificados para cualquier dominio. Al montar tu mini-PKI, marca siempre la hoja comoCA:FALSE(el valor por defecto si no añades la extensión de CA) y limita los usos.
Ejercicios
Ejercicio 1 — Autopsia de un certificado real. Con openssl s_client + openssl x509, descarga el certificado de un dominio público que uses a diario y responde: (a) ¿quién es el issuer y quién el subject? (b) ¿cuántos nombres hay en el SAN? (c) ¿cuántos días de vida total tiene? (d) ¿cuál es su huella SHA-256?
Ejercicio 2 — Auditor en Python. Escribe una función auditar_certificado(ruta_pem: str) -> list[str] que cargue un certificado con cryptography.x509 y devuelva una lista de avisos: "CADUCADO" si ya expiró, "SIN_SAN" si no tiene la extensión SAN, y "VIDA_EXCESIVA" si Not After − Not Before supera 398 días (el máximo que aceptan los navegadores hoy). Pruébala con el certificado del ejercicio 1 y con tu portal.crt del laboratorio.
Ejercicio 3 — Mallory contra tu mini-PKI. En tu laboratorio, genera una segunda CA (mallory-ca.key / mallory-ca.crt, CN MalloryClinic CA) y emite con ella un certificado para portal.medinube.example (¡Mallory puede escribir lo que quiera en sus propios certificados!). Comprueba con openssl verify -CAfile ca.crt mallory-portal.crt que no valida contra la raíz legítima de MediNube, y razona por qué esto modela exactamente la derrota del MITM.
Soluciones
Solución 1. Ejemplo de sesión (los valores concretos dependerán del dominio y la fecha):
openssl s_client -connect ejemplo.com:443 -servername ejemplo.com \
</dev/null 2>/dev/null | openssl x509 -out /tmp/cert.pem
openssl x509 -in /tmp/cert.pem -noout -subject -issuer # (a)
openssl x509 -in /tmp/cert.pem -noout -ext subjectAltName # (b) cuenta las entradas DNS:
openssl x509 -in /tmp/cert.pem -noout -dates # (c) resta notAfter - notBefore
openssl x509 -in /tmp/cert.pem -noout -fingerprint -sha256 # (d)Es habitual encontrar issuers como Let's Encrypt (C=US, O=Let's Encrypt, CN=...) — una CA intermedia, no la raíz: recuerda que la hoja casi nunca la firma la raíz directamente.
Solución 2.
from datetime import datetime, timedelta, timezone
from cryptography import x509
def auditar_certificado(ruta_pem: str) -> list[str]:
with open(ruta_pem, "rb") as f:
cert = x509.load_pem_x509_certificate(f.read())
avisos = []
ahora = datetime.now(timezone.utc)
if cert.not_valid_after_utc < ahora:
avisos.append("CADUCADO")
try:
cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
except x509.ExtensionNotFound:
avisos.append("SIN_SAN")
vida = cert.not_valid_after_utc - cert.not_valid_before_utc
if vida > timedelta(days=398):
avisos.append("VIDA_EXCESIVA")
return avisosDetalles que importan: usamos las propiedades _utc y comparamos con datetime.now(timezone.utc) (mezclar datetimes con y sin zona lanza TypeError); el SAN se detecta capturando ExtensionNotFound, no mirando None. Tu portal.crt de laboratorio debería devolver []; muchos certificados antiguos de intranets devuelven SIN_SAN y VIDA_EXCESIVA a la vez.
Solución 3.
cd ~/medinube-lab/pki
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out mallory-ca.key
openssl req -x509 -new -key mallory-ca.key -days 1825 \
-subj "/CN=MalloryClinic CA" \
-addext "basicConstraints=critical,CA:TRUE" -out mallory-ca.crt
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out mallory-portal.key
openssl req -new -key mallory-portal.key \
-subj "/CN=portal.medinube.example" \
-addext "subjectAltName=DNS:portal.medinube.example" -out mallory-portal.csr
openssl x509 -req -in mallory-portal.csr -CA mallory-ca.crt -CAkey mallory-ca.key \
-CAcreateserial -days 90 -copy_extensions copy -out mallory-portal.crt
openssl verify -CAfile ca.crt mallory-portal.crt
# → error 20: unable to get local issuer certificate ← FALLA, como debe ser
openssl verify -CAfile mallory-ca.crt mallory-portal.crt
# → OK ← solo valida para quien confíe en la CA de MalloryRazonamiento: el contenido del certificado de Mallory es idéntico al legítimo (mismo SAN, misma estructura); lo que no puede falsificar es la firma de una raíz en la que la víctima ya confía, porque no tiene su clave privada. La confianza no está en lo que el certificado dice, sino en quién lo firma — exactamente la propiedad de no falsificación de las firmas de 04-03, elevada a infraestructura.
Conclusión
La grieta que perseguía a MediNube desde 04-01 tiene por fin un cierre técnico: un certificado X.509 ata clave pública e identidad con la firma verificable de una CA, y la cadena de confianza (raíz offline en el trust store → intermedia → hoja) permite validar esa firma sin haber intercambiado nada en persona. Has aprendido a leer la anatomía de un certificado (con el SAN como campo que de verdad manda), a inspeccionarlo con openssl x509 -text y con cryptography.x509, a generar una CSR sin que la clave privada salga de casa, a distinguir DV/OV/EV, y has montado una mini-PKI de laboratorio con la que comprobaste, en tu propia terminal, que los certificados de MalloryClinic no validan. También viste que el modelo tiene grietas propias (DigiNotar) y vigilantes (Certificate Transparency).
Pero un certificado, quieto en un fichero, no protege nada. Falta el protocolo que lo pone en acción en cada conexión de Ana Pérez con el portal: el que presenta el certificado, acuerda claves efímeras con ECDH, deriva claves con HKDF y cifra con AEAD. Te sonarán todas las piezas, porque llevas cuatro módulos construyéndolas. En la próxima lección, 05-02: TLS en la práctica, las verás encajar en el protocolo de seguridad más usado del mundo.
Curso de Criptografía Aplicada
Módulo 1: Fundamentos de la Criptografía
- Qué es la criptografía y para qué sirve
- Codificación, ofuscación y cifrado
- Aleatoriedad y entropía
- El principio de Kerckhoffs y las reglas de oro
Módulo 2: Criptografía Simétrica
- Cifrado simétrico: AES y ChaCha20
- Modos de operación
- Cifrado autenticado (AEAD)
- Derivación de claves (KDF)
Módulo 3: Hashes, MAC y Contraseñas
- Funciones hash criptográficas
- Autenticación de mensajes con HMAC
- Almacenamiento seguro de contraseñas
Módulo 4: Criptografía Asimétrica
- Fundamentos de clave pública y RSA
- Criptografía de curva elíptica
- Firmas digitales
- Intercambio de claves: Diffie-Hellman
- Cifrado híbrido
Módulo 5: PKI, Certificados y TLS
- Certificados X.509 y autoridades de certificación
- TLS en la práctica
- Gestión del ciclo de vida de certificados
