Hace unos días, en el artículo sobre cómo mitigar PHP2Shell, mencionaba de pasada el caso de los certificados de eMudhra. El equipo de Go ha publicado ahora cómo respondió al incidente, y es buen momento para contarlo entero: qué pasó, por qué afectaba a Go de una forma particular y qué conviene revisar fuera del navegador.
Qué pasó
eMudhra es una autoridad de certificación india cuya raíz está en la mayoría de almacenes de confianza. Ocho de sus certificados intermedios (seis para TLS y dos para firma de código) tenían claves privadas absurdamente débiles. Según el análisis de Scott Helme, eran claves de curva elíptica P-384 en las que el escalar privado, que debería ser un número aleatorio de 384 bits, era 47 bytes a cero seguidos de un único byte. Sólo hay 255 valores posibles.
Recuperar una clave así consiste en multiplicar el punto generador de la curva por 1, por 2, por 3… hasta que el resultado coincida con la clave pública del certificado. Lo he probado con una clave generada con el mismo defecto:
from cryptography.hazmat.primitives.asymmetric import ec
# Clave "de CA" con un escalar privado de un solo byte
weak = ec.derive_private_key(155, ec.SECP384R1())
public = weak.public_key().public_numbers()
# Recuperarla: probar d = 1, 2, 3... hasta que d·G coincida
for d in range(1, 256):
candidate = ec.derive_private_key(d, ec.SECP384R1()).public_key().public_numbers()
if candidate == public:
print(f"Clave privada recuperada: d = {d}")
break
En mi máquina tarda menos de medio segundo, incluido arrancar Python. Con cualquiera de esas claves se puede firmar un certificado TLS válido para cualquier dominio, sin demostrar que lo controlas, y hacer un ataque de intermediario contra quien confíe en esa cadena.
Según eMudhra, el origen fue un fallo en el script de la ceremonia de generación de claves: se creó un segundo par de claves débil en el HSM y, por nombres parecidos, se usó el equivocado para firmar. Los intermedios se crearon en abril de 2025, Sectigo los firmó también en cruzado en mayo de 2025, y son válidos hasta 2039. Durante unos 17 meses, esas claves fueron en la práctica públicas para quien se molestara en mirar.
La respuesta, en pocas horas
Un investigador lo comunicó el sábado 17 de octubre a Apple, Google, Microsoft, Mozilla y eMudhra, y la reacción fue rápida:
| Quién | Qué hizo |
|---|---|
| Google Chrome | Bloqueó los intermedios con una actualización de CRLSet la misma noche del 17 |
| Mozilla | Los añadió a OneCRL en Firefox horas después del aviso, y el 20 publicó una actualización de NSS que retira la raíz de eMudhra |
| Apple | Bloquea los 12 certificados (seis intermedios, cada uno firmado por dos raíces) desde el 19, sin necesidad de actualizar el sistema, y la raíz emSign ECC Root CA - G3 desde el 23 |
| Go | Publicó Go 1.27.3 y 1.26.10 el 19, unas siete horas y media después de enterarse |
Por qué Go tuvo que intervenir
Aquí está lo interesante del caso de Go. Su biblioteca estándar verifica los certificados TLS de net/http y crypto/tls, y lo hace de forma distinta según el sistema: en Windows y macOS delega en las APIs de la plataforma, que reciben los bloqueos de Microsoft y Apple; pero en Linux usa su propio verificador, porque no hay un mecanismo común del sistema para comprobar revocaciones. Un binario Go en un servidor Linux habría seguido aceptando certificados falsificados hasta que se actualizara el almacén del sistema, y eso depende de cada distribución.
Así que el equipo de seguridad de Go decidió bloquear esos intermedios en el propio verificador y estrenó su vía de publicación URGENT, que nunca había usado. Su cronología del 19 de octubre, en hora del Pacífico:
| Hora | Paso |
|---|---|
| 11:45 | Un investigador independiente avisa al equipo de seguridad de Go |
| 12:30 | Mozilla lo confirma por su cuenta, pide no divulgarlo aún y anima a publicar el arreglo cuanto antes |
| 14:00 | Parche terminado, probado con certificados firmados con las claves recuperadas |
| 15:00 | Empieza la publicación de 1.27.3 y 1.26.10 |
| 19:13 | Versiones publicadas y anunciadas |
El anuncio fue deliberadamente opaco: hablaba de “preocupaciones de seguridad concretas sobre su funcionamiento” para no dar pistas a los atacantes. A la mañana siguiente, varios investigadores ya habían publicado las claves.
Lo que más me gusta del artículo es la parte de lecciones aprendidas. De esas siete horas y media, el proceso de publicación en sí se llevó casi cuatro horas y cuarto, por problemas que nunca habían molestado cuando una publicación se repartía en varios días. Ya lo han mejorado, y reconocen que podrían haber preavisado de la publicación urgente al empezarla, para que la gente tuviera al menos unas horas para prepararse. Y anuncian que priorizarán un soporte de revocación en Linux equivalente al de otras plataformas, para lo que estudian el proyecto upki.
Qué revisar
Si tienes binarios Go que hablen TLS, directamente o a través de dependencias como gRPC, hay que recompilarlos con Go 1.27.3 o 1.26.10 o posterior. El equipo de Go indica cómo comprobarlo:
# Versión de la toolchain
go version
# Versión de Go con la que se compiló un binario
go version -m ./mi-binario
# Comprobar si el binario está afectado
GOTOOLCHAIN=go1.27.3+auto go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck -mode=binary ./mi-binario
Recompilar es la clave: un binario Go lleva la biblioteca estándar dentro, así que actualizar Go en la máquina no cambia nada en los binarios que ya están desplegados. Lo mismo vale para las imágenes Docker que llevan un binario compilado.
Si usas macOS desde la terminal, la nota de Apple explica un detalle que no esperaba: Safari, Mail y las aplicaciones que usan los frameworks de Apple ya bloquean estos certificados, pero curl, git, python3, ruby, perl y openssl incluidos en macOS o en las Command Line Tools de Xcode verifican contra /etc/ssl/cert.pem, sin consultar el bloqueo de Apple ni la revocación. Ese fichero se actualizará en una próxima versión de macOS, y mientras tanto Apple da los comandos para quitar las raíces a mano. Para comprobar si siguen ahí:
openssl crl2pkcs7 -nocrl -certfile /etc/ssl/cert.pem \
| openssl pkcs7 -print_certs -noout \
| grep -E 'emSign ECC Root CA - G3|AAA Certificate Services'
Ojo con un matiz que la propia nota explica: quitar sólo la raíz de eMudhra no basta, porque los seis intermedios firmados en cruzado por Sectigo se siguen validando a través de AAA Certificate Services. Quitar también esa raíz puede romper la verificación de otros sitios legítimos que dependan de ella, así que conviene leer la nota completa antes de tocar nada.
En general, fuera del navegador. Scott Helme comprobó que curl, Python y Node, con su configuración por defecto, aceptaban un certificado falsificado para un dominio real tanto en Linux como en macOS. Los navegadores estaban protegidos porque exigen Certificate Transparency y reciben los bloqueos, pero muchos clientes sólo miran si la cadena llega a una raíz de su lista. En servidores y contenedores, eso significa actualizar el paquete de certificados raíz de la distribución en cuanto lo publique, y no olvidar las aplicaciones que traen su propio almacén.
Lo que me llevo
Este caso me parece un buen recordatorio de que la confianza en TLS se rompe por el eslabón más tonto: no hizo falta ningún ataque sofisticado, sólo un script que generó la clave equivocada y nadie que comprobara que la clave tenía el tamaño que debía.
También deja ver bien la diferencia entre el navegador y todo lo demás. Los navegadores tienen mecanismos para reaccionar en horas, pero la mayoría del tráfico TLS de un servidor sale de clientes como curl, bibliotecas HTTP o binarios Go que confían en una lista de raíces y poco más. Por eso valoro tanto que Go actuara en su propio verificador sin esperar a las distribuciones, y que lo cuente con detalle, incluido lo que salió peor de lo previsto.
Fuente: Our Response To The eMudhra Key Disclosure Incident, Roland Shoemaker y el equipo de seguridad de Go. Más contexto en About the compromised eMudhra intermediate CAs, Apple, y en Eight bits of trust, Scott Helme.




