Certificate pinning. El qué, el cómo y el porqué (y IV)

martes, 10 de septiembre de 2013

Terminamos la serie de "certificate pinning" con un ejemplo práctico de cómo funciona en Chrome la asociación de certificados. Dispone de una página de pruebas a la que se accede a través de chrome://net-internals/#hsts y en la que se puede experimentar con diferentes posibilidades del navegador.

Desde mayo de 2011 se incluyen posibilidades de pinning en Chrome. Todo a raíz de los problemas con los certificados que se vinieron observando desde 2010 y que minaron la confianza en la estructura SSL.
Para empezar, lo que hicieron fue incluir en su código fuente los datos de las autoridades que firman certificados de servicios de Google habitualmente (Verisign, Google Internet Authority, Equifax y GeoTrust), descartando DigiCert, que lo hacía hasta ese momento pero, por incidentes variados, ya no resultó de confianza.

Detalles de cómo funciona

Un detalle importante es saber que en Chrome, si un certificado raíz ha sido instalado por el usuario, se ignora el "pinning" y lo deja pasar. Esto supone un potencial punto débil en su implementación. La razón es que existen soluciones antivirus o proxies en empresas que necesitan instalar certificados en el equipo para inspeccionar el tráfico SSL u otras razones... y Chrome no quiere romperlas. No es un asunto fácil porque supone un verdadero punto oscuro en el entramado, y realmente le están dando vueltas todavía a cómo solucionarlo, como se observa en las listas públicas oficiales.

Dejando de lado este detalle, otro aspecto importante es que Chrome no "mira" el certificado completo para comprobar un certificado, sino la clave pública asociada a él. Esto es interesante. Normalmente, se suele tomar el hash del certificado completo como "thumbprint", "fingerprint" o huella digital para identificarlo. Esto incluye el hash de todos y cada uno de los datos del certificado, no el hash de la clave pública en sí. Lo podemos comprobar fácilmente con OpenSSL. El resultado de su fingerprint es el mismo que el de calcular el SHA1 del fichero con la representación en base64 del formato DER del certificado, y a su vez igual al campo "Huella digital" que muestra Microsoft al ver un certificado (y lo que usa EMET). Se resume en esta imagen.

Huella digital de un certificado cualquiera, cómo se calcula y de dónde sale
Pero Chrome no usa el certificado completo. Piensa que es mejor (da mayor flexibilidad) utilizar la clave pública, o más exactamente lo que se entiende por SubjectPublicKey. Así, si por ejemplo cambia la fecha de caducidad o cualquier otro dato, el "pinning" sigue siendo válido. Pero también, por otras razones criptográficas, no calcula el hash solo de la clave pública "pura" sino de un campo llamado SubjectPublicKeyInfo (junto o separado, según la programación), que incluye otras características de la clave pública (algoritmo, módulo...). Se observa claramente al consultar cualquier certificado con este comando:
Sacando el SubjectPublicKeyInfo de un certificado
Otro detalle a tener en cuenta es que si bien EMET solamente permitía asociar dominios raíz, Chrome permite asociar cualquier certificado en cualquier punto de la cadena, y será válido. Esto también resulta un arma de doble filo. Se supone que Chrome dice que todo es correcto con la única condición de que aparezca el certificado "asociado" en cualquier punto de la cadena, no necesariamente en la raíz o la hoja... Si no se utiliza bien, puede dejar hueco a los ataques.

Probándolo todo

Para probarlo debemos aprender a calcular esos hashes que definen a los certificados. Es "simple". Se guarda el certificado que se quiere asociar, y se saca su "clave pública" tal y como la calcula openssl:

openssl x509 -pubkey -noout -in certificado.crt

Los certificados siempre deben estar codificados en binario. Se eliminan los "BEGIN" y "END", los retornos de carro y se pasa de nuevo a binario (decodificando el base64). De ese binario se calcula el hash SHA1. Ese hash, en binario, se pasa de nuevo a base64. Para hacerlo en python, resumiendo, hay un script aquí (solo válido con la rama 2 de python).

Ahora que tenemos esa información, podemos probar que funciona desde chrome://net-internals/#hsts. Se introduce, según las indicaciones, un dominio y el hash calculado de su certificado. Esto hará que Chrome almacene esa asociación temporalmente. A partir de ahora, si visitamos esa web, aparecerá este mensaje bloqueando el acceso (mucho mejor que el de EMET, porque realmente aquel era un simple aviso, y este impide el paso).

En el caso de que el certificado no sea correcto, Chrome mostrará este error
Pero solo funcionará si no hemos visitado la página previamente al experimento. Curiosamente, como esta consola es de pruebas, el dominio que no lo solicita explícitamente (que pocos lo hacen) se borra de esa tabla y Chrome lo olvida. Por tanto, esta consola no sirve para asociar sino como banco de pruebas de qué pasaría si los dominios requiriesen el "pinning" o de qué pasaría si nos estuvieran espiando realmente con un dominio que ya viene "asociado" de serie en Chrome.

Un RFC reciente

En el borrador de RFC en https://tools.ietf.org/html/draft-ietf-websec-key-pinning se está estudiando que el pinning se incluya en HTTP. O sea, que los dominios envíen información HTTP al navegador sobre sus certificados. Algo del tipo una cabecera con toda la información necesaria:

Public-Key-Pins: pin-sha1=”4n972HfV354KP560yw4uqe/baXc=”;pin-sha1=”qvTGHdzF6KLavt4PO0gs2a6pQ00=”;pin-sha256=”LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=”;max-age=10000; includeSubDomains

Que al final se parece a lo que ya tiene Chrome, pero sin tener que modificar el código fuente del navegador, como se viene haciendo hasta ahora para incluir la asociación, sino dotando a las webs de un mecanismo permita solicitarlo.

Sergio de los Santos
ssantos@11paths.com


No hay comentarios:

Publicar un comentario