Autoridades de Certificación personalizadas en Java: la pesadilla detrás de una VPN
Y aquí estamos, rascandonos la cabeza y preguntándonos por qué no hemos podido conectarnos a esa API por la que llevamos tiempo intentando hacer funcionar.
Yo, a la 1 am y después de 6 horas mirando documentacion de Spring
Una de las cuestiones a tener en cuenta, cuando trabajamos detras de una VPN corporativa, es que es probable que muchos recursos estén bloqueados, y que otros simplemente han sido “alterados” o sobrepasados por la configuracion de la VPN.
Este caso puede que no sea visible, ya que los grandes proveedores de VPN usan Autoridades de Certificacion (CA) que están incluidas en los navegadores más comunes como Chrome, Firefox, Safari o Edge.
Sin embargo este no siempre es el caso cuando hablamos de la Máquina Virtual de Java (JVM) y sus certificados CA incluidos.
Como identificar que el error procede de una CA no configurada en tu JVM?, no tenemos una lista extensiva de errores, pero si podemos dar un indicio con el siguiente mensaje de error:
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Y aunque el error nos dice algo relacionado con una “ruta de certificacion”, si es la primera vez que ves un error de estos puede que no sepas como solucionarlo.
Lo primero que debemos saber, es donde se almacenan los certificados de CA que usa nuestra JVM/JDK, que comúnmente se ubican en esta ruta:
$JAVA_HOME/lib/security/cacerts
Donde $JAVA_HOME
es la ruta de instalacion de nuestra JVM/JDK, la ubicacion puede variar dependiendo del Sistema Operativo (SO), pero es una buena práctica que la variable de entorno siempre este definida. En el caso de Linux y MacOs, es posible ejecutar el siguiente comando para saber si la tenemos instalada y que nos diga donde se encuentra:
$ /usr/libexec/java_home
/Users/razor/Library/Java/JavaVirtualMachines/openjdk-21.0.2/Contents/Home
En caso que nuestra variable no este correctamente configurada, podriamos ejecutar esta linea para asignarla de manera efectiva:
$ export JAVA_HOME=$(/usr/libexec/java_home)
Ahora que tenemos configurado nuestro entorno de JVM, debemos obtener una copia del certificado que usa la API que deseamos configurar. Para efectos académicos, usaremos la API de libros de Google.
Para obtener una copia del certificado, usaremos OpenSSL con el siguiente comando:
$ openssl s_client -connect books.googleapis.com:443 < /dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > public.crt
Explicando lo que hace el anterior comando, va a procesar el certificado SSL que será provisto al navegar a https//books.googleapis.com
(como es conexion al puerto 443, se infiere que es HTTPS), y lo guardará en el archivo public.crt
, después de extraer la informacion real del certificado.
Ahora que tenemos el certificado, solo resta agregarlo a la cadena de certificacion de nuestra JVM, lo que haremos con el comando keytool que se incluye en nuestra JVM:
$ $JAVA_HOME/bin/keytool -import -alias books.googleapis.com -cacerts -file public.crt -noprompt
Nuevamente explicando el comando, ejecutamos la utilidad keytool
desde nuestra ubicacion de instalacion de JVM, importando (-import
) el certificado ubicado en el archivo public.crt
(-file
) en nuestra lista de CAs de confianza (-cacerts
), usando el alias books.googleapis.com
.
El parámetro -alias
nos permite administrar posteriormente dicho certificado, ya sea que toque actualizarlo, o cuando no necesitemos mas porque estemos fuera de la VPN. El parámetro -noprompt
es para no tener que confirmar la acción, algo util si quisieras incluir estas instrucciones en un solo archivo y ejecutarlas periódicamente.
Para probar que nuestro problema ha sido resuelto, podriamos ejecutar simplemente nuestra aplicación de nuevo, no obstante pueda que nuestra aplicación sea demasiado grande y nuestro tiempo muy valioso.
Una sugerencia sería usar SSLPoke, una utilidad de Atlassian, que nos ayuda a verificar si los certificados instalados son correctos para conectarnos a un sitio:
$ $JAVA_HOME/bin/java SSLPoke books.googleapis.com 443
Successfully connected
Ya con estos pasos descritos anteriormente, el error causado por los certificados CA detras de una VPN debería estar resuelto.