Resumen

  • El árbol de políticas de RFC 5280 puede copiar el mismo estado lógico una y otra vez cuando varias políticas se mapean entre sí; su peor caso crece exponencialmente con la profundidad de la cadena.
  • RFC 9618 usa un grafo acíclico dirigido con nodos compartidos. Mantiene la validez y las políticas finales, pero limita el tamaño a una relación lineal con las políticas y mapeos de entrada.
  • La conformidad operativa requiere demostrar tanto equivalencia semántica como límites de CPU, memoria y cola ante cadenas adversarias.

La pasarela de autenticación funcionaba con normalidad hasta que llegó una cadena cliente deliberadamente construida. El mensaje era pequeño. Sus firmas no concedían acceso. Sin embargo, varios procesos quedaron ocupados expandiendo combinaciones de políticas; las conexiones legítimas esperaron detrás.

El atacante no había roto X.509. Había utilizado la autoridad de cálculo que el validador entregaba a cualquier cadena presentada antes de autenticarla. RFC 9618 corrige esa asimetría sin cambiar la respuesta que la política debe producir.

De OID a decisión

La extensión de políticas de certificado contiene identificadores OID y, opcionalmente, calificadores. Una autoridad certificadora puede declarar qué políticas cubre su certificado, imponer restricciones y mapear una política de su dominio a otra del dominio de la autoridad subordinada. La aplicación aporta el conjunto que está dispuesta a aceptar.

Durante la validación, RFC 5280 calcula qué políticas atraviesan toda la cadena. No es lo mismo que buscar la cadena. RFC 4158 trata la construcción de caminos candidatos; RFC 9618 se ocupa del procesamiento de políticas dentro de un camino ya suministrado.

RFC 5280 representaba el estado como valid_policy_tree. Cada ruta de mapeo tenía su propia rama. Cuando dos políticas de un certificado conducen a la misma política del siguiente, el árbol crea dos copias del mismo estado. En el nivel posterior vuelve a copiar sus hijos.

Con dos OID por certificado y todos los mapeos posibles entre ambos, cada profundidad duplica el número de rutas. El atacante envía una cadena cuyo tamaño aumenta de forma moderada; el validador genera un árbol que crece mucho más deprisa. En un servidor TLS con certificados de cliente, esa diferencia puede negar servicio sin que la cadena llegue a ser aceptada.

Los casos citados por el RFC demuestran el efecto. El registro de OpenSSL vincula CVE-2023-0464 al consumo exponencial durante la comprobación de políticas y documenta un límite de nodos. El aviso de Apple vincula CVE-2023-23524 con el procesamiento de un certificado malicioso y la denegación de servicio. El historial público de OpenSSL conserva la mitigación.

Un nodo compartido cambia el coste, no el significado

El valid_policy_graph de RFC 9618 conserva un solo nodo para cada OID y profundidad. Si varias políticas anteriores llegan al mismo OID, ese nodo tiene varios padres. Los descendientes se almacenan una vez. El resultado es un grafo acíclico dirigido, no un árbol de rutas duplicadas.

La información no desaparece. El árbol antiguo equivale a enumerar todos los caminos desde la raíz hasta las hojas del grafo. Para saber qué políticas finales son alcanzables, el validador no necesita materializar cada camino. Por eso el nuevo algoritmo conserva tanto la validez de la cadena como el conjunto final de políticas.

Su límite sí cambia: el número de nodos y aristas queda ligado linealmente al total de políticas y mapeos codificados. Esto no promete tiempo constante ni hace inocua una entrada enorme. Evita que una entrada compacta ordene al sistema construir un objeto exponencialmente mayor.

La norma compartida puede así permanecer delgada. Define invariantes de anyPolicy, mapeos, contadores, poda y salida; no exige que todos los productos utilicen la misma estructura de memoria. La libertad local termina cuando una optimización altera el resultado o deja el coste sin límite.

El árbol como salida heredada

Hay una segunda superficie: RFC 5280 describía valid_policy_tree completo como parte de la salida. Incluso con un algoritmo interno seguro, una API antigua puede pedir que el grafo se expanda al árbol histórico. La explosión reaparece en el borde de salida.

RFC 9618 depreca ese resultado y recomienda devolver los conjuntos de políticas limitados por la autoridad y por el usuario. Estos conjuntos suelen contener la decisión que necesita la aplicación, sin obligarla a recibir todas las rutas equivalentes.

La reconstrucción diferida del árbol sigue permitida para consumidores heredados, pero necesita su propio control. El cálculo diferido no reduce el peor caso; solo cambia quién pulsa el botón y cuándo. Si se mantiene, debe quedar fuera del camino común, con cuota, aislamiento y telemetría.

Esta decisión revela quién gobierna realmente la disponibilidad. Si una herramienta de diagnóstico sin propietario obliga a todos los validadores a preservar un objeto inseguro, esa herramienta ha adquirido poder sobre cada autenticación. La compatibilidad debe responder a una necesidad identificada, no a la inercia.

Los límites también cambian la frontera

Restringir profundidad o nodos puede proteger implementaciones antiguas. Pero un límite bajo rechaza caminos legítimos; uno alto conserva espacio para el ataque. RFC 9618 señala que más políticas por certificado pueden producir una escala próxima a O(N^(profundidad/2)) incluso cuando la profundidad está acotada.

Un tope de nodos es útil solo si actúa antes de agotar recursos y falla de forma estable. La evidencia debe mostrar el nodo en que se detuvo, memoria y CPU acumuladas, código de error, reintentos y efecto sobre otras conexiones. Sin esa cronología, “tenemos un límite” no permite evaluar el servicio.

Tampoco basta con desactivar políticas. Cuando las extensiones correspondientes son críticas, un motor que no las procesa debe tratarlas como desconocidas y rechazar el certificado. El bit de criticidad es un contrato firmado, no una opción para ahorrar trabajo ignorando significado.

Pruebas que unen semántica y capacidad

La primera batería compara decisiones. Debe incluir políticas simples, varios mapeos, anyPolicy, política explícita, inhibición de mapeos, inhibición de anyPolicy, poda y resultados vacíos. Para cada vector, se conserva aceptación o rechazo y el conjunto final. Un validador que “mejora” su velocidad eliminando casos válidos no ha migrado correctamente.

La segunda batería fuerza la complejidad. Varía la profundidad, el número de OID y la densidad del mapeo por separado. Registra bytes, nodos, aristas, memoria máxima, asignaciones, tiempo de CPU, tiempo real, causa de cierre y ocupación de cola con concurrencia. Así se ve si el coste sigue la entrada o si aparece otra multiplicación oculta.

Cada prueba semántica necesita su traza de recursos, y cada prueba hostil necesita su decisión exacta. Separarlas permite dos falsos éxitos: rapidez obtenida cambiando el significado, o compatibilidad obtenida conservando el agotamiento.

La última prueba es de ejecución. Hay que identificar biblioteca cargada, proceso, configuración de políticas y uso de la API heredada. Un RFC aprobado o un paquete instalado no garantiza qué código tomó la decisión en producción.

RFC 9618 no convierte toda validación X.509 en lineal. La búsqueda de caminos, la criptografía, la revocación, el análisis y la autorización tienen otros presupuestos. Su valor reside en ser preciso: quita una amplificación concreta sin ceder la respuesta. Esa precisión permite exigir un recibo igualmente concreto.

Fuentes