miércoles, 17 de junio de 2015

TLS/SSL y los problemas con los certificados

Desde hace unos años vengo trabajando con certificados, de SSL/TLS y de firma electrónica para eFactura, ambos funcionan con un esquema similar, solo que uno es para asegurar conexiones y el otro para asegurar la legitimidad de los documentos intercambiados.

Ahora me dispongo a compartir parte de lo que he aprendido, intentando dar una guía rápida que permita entender y solucionar rápidamente los problemas más comunes al trabajar con certificados. Vale la pena dedicarle 5 o 10 minutos para entenderlo porque luego ahorra mucho tiempo y frustraciones.

En mayor o menor medida todos sabemos que cuando vemos el candadito en el navegador, nos indica que es una conexión segura, esto tiene 2 aspectos: la autenticación y la encriptación, ya que no alcanza con encriptar si no sabemos si estamos hablando con quien queremos o un impostor. Siendo la autenticación la primer etapa, es donde surge la mayor parte de los problemas porque requiere tener instalados adecuadamente los certificados.

Esta es una simplificación de como se da la conversación entre un cliente y un servidor:


Los certificados raíz representan Entidades Certificadoras, estas son empresas en las que confiamos que solo emiten certificados a entidades legítimas, por ejemplo confiamos en que no le va a emitir un certificado para example.com a alguien que no sea dueño de ese dominio.

Entonces el servidor responde con su cadena (el certificado raíz no es necesario) y el cliente verifica en su almacén de certificados de confianza si tiene el certificado raíz que completa esa cadena, una vez que lo encuentra usa herramientas criptográficas para asegurarse de que todos los certificados (intermedios y final) son legítimos. La autenticación de cliente es opcional, si el servidor la requiere el cliente le envía su certificado y el servidor hace la validación análoga, pero la mayoría de los servicios web no requieren este tipo de autenticación.

Algunas apreciaciones:
  • El Trust store y el Key store pueden ser el mismo almacén, pero están diferenciados porque tienen funciones distintas. Por ejemplo en Java se configuran por separado y en Windows se manejan todos juntos categorizados según su uso.
  • Un servidor puede actuar como cliente de otro, entonces en el servidor también hay que configurar el Trust store adecuadamente. Esto es muy frecuente en sistemas web que requieren servicios de terceros.
  • En SSL/TLS un servidor debe enviar la cadena con el certificado final y todos los certificados intermedios (puede ser más de uno), el certificado raíz lo puede enviar pero no es necesario porque el cliente igual debe verificar que esté en su Trust store.
  • En firma electrónica es común que los certificados intermedios no se incluyan, por lo que también hay que agregarlos al Trust store. Esto no es lo ideal pero si queremos validar estos documentos no tenemos alternativa.
¿Por qué son necesarios los certificados intermedios?
La razón principal es seguridad. Una entidad certificadora utiliza su clave privada para emitir certificados intermedios, por o que debe ser guardada con altas medidas de seguridad, si se utilizara cada vez que se emite un certificado final significa que tendría un nivel de exposición alto comparado con utilizarla solo cuando se necesita un nuevo certificado intermedio.
Además si se viera comprometido el certificado intermedio, al entidiad certificadora puede revocarlo y emitir un nuevo certificado intermedio, de esta forma, el certificado raíz que ya está instalado en millones de equipos en todo el mundo sigue siendo válido.

Hasta aquí la parte conceptual, ahora algo de información específica de Java.

Troubleshooting en Java

  • java.io.IOException: Keystore was tampered with, or password was incorrect:
    Lo más probable es que tengamos mal cargada la password y por eso no puede leer el Keystore.
  • javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed:
    El error indica que no se pudo construir la cadena desde el certificado final hasta un certificado raíz de confianza. Lo más probable es que no tengamos cargado el certificado raíz o los intermedios en nuestro Trust store. Otra posibilidad es si nos queremos conectar a un servicio con certificado autofirmado, por ejemplo un servidor de mail interno, en ese caso tenemos que importar el certificado autofirmado (final y raíz a la vez).
    Otro escenario común en Uruguay es con los servicios que están certificados por el Correo Urugayo, como los certificados raíz no vienen precargados en Java tenemos que importarlos manualmente.
  • javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate:
    Este error puede aparecer cuando el servidor nos exige autenticación de cliente y nuestro sistema no le envía un certificado adecuado.

Truststore y Keystore en Java

Se configuran con las siguientes propiedades:
  • javax.net.ssl.trustStore: "/path/to/keystore"
  • javax.net.ssl.trustStoreType: "JKS"
  • javax.net.ssl.trustStorePassword: "changeit"
  • javax.net.ssl.keyStore: "/path/to/keystore"
  • javax.net.ssl.keyStoreType: "PKCS12"
  • javax.net.ssl.keyStorePassword: "contraseña"
y se puede configurar de las siguientes formas:
  • Como parámetro de Java: -Djavax.net.ssl.trustStore=/path/to/keystore
    Recomendado.
  • Con código: System.setProperty("javax.net.ssl.trustStore", "/path/to/keystore");
    No recomendado porque si el Truststore ya fue inicializado esta asignación no va a tener ningún efecto.
En Java el Truststore por defecto se llama "cacerts" y viene cargado con entidades certificadoras donde se encuentran las más comunes. La ubicación varía en cada sistema operativo, en Windows está en la carpeta lib\security\cacerts se la instalación de Java, mientras que en linux varía de una distribución a otra, en general el archivo lib/security/cacerts suele apuntar a una ruta centralizada para cualquier instalación de Java, por ejemplo /etc/pki/java/cacerts.

Además tener en cuenta que los servidores web puede utilizar las ubicaciones por defecto o establecer su propia ubicación, por ejemplo Tomcat usa la por defecto de Java mientras que GlassFish asigna su propia ruta.

Como agregar un certificado raíz o autofirmado al Truststore de Java

Se necesita la herramienta keytool que viene con la JDK (no alcanza con la JRE).

Primero hay que encontrar el archivo cacerts que queremos actualizar.

Para verificar los certificados que hay dentro podemos usar el comando:
> keytool -list -keystore cacerts
la contraseña por defecto es: changeit
Para importar un certificado:
> keytool -import -alias "Nombre del Certicado" -file CertificadoRoot.cer -keystore cacerts
el alias es solo un nombre descriptivo con el que quedará guardado, no es necesario que coincida con nada, solo un nombre con el que nos sea fácil volver a encontrarlo.

Conozco el sitio al que me quiero conectar. ¿Cómo puedo obtener su certificado raíz?

Con un navegador es fácil investigar el certificado, pongo un ejemplo con Chrome, pero en todos se hace de forma similar.

Luego usamos Copy to File para guardarlo y poder importarlo a nuestro almacén.

¿Y si el browser no me muestra la cadena?
Significa que el browser tampoco tiene instalado el certificado raíz, en ese caso vamos a la pestaña Details y le buscamos el Issuer, luego se puede buscar la página oficial del Issuer para descargar sus certificados.


Espero que esta guía les haya sido útil, no entra demasiado en profundidad sino que apunta a explicar conceptualmente como funciona el sistema de certificados y a resolver los problemas más comunes con los que me he encontrado yo y otras personas a las que he ayudado.