Consultar y gestionar almacenes de certificados de confianza Java
Los almacenes de certificados de confianza de Java o truststores son archivos binarios en los que se almacenan claves públicas de Autoridades de Certificación (CA). Las aplicaciones Java, a la hora de hacer peticiones a sitios web que ofrecen su contenido (APIs REST, APIs SOAP, contenido HTML, etc.) a través del protocolo HTTPS, comprueban como parte del proceso de negociación TLS/SSL que el certificado de ese sitio se encuentra emitido por una de las CA que se encuentren en su truststore. Vamos a ver cómo consultar y gestionar el contenido de estos almacenes.
Contenido
En cualquier equipo con un JRE/JDK instalado, el truststore predeterminado se encuentra dentro del directorio donde está ubicado el JRE (frecuentemente referenciado por la variable JAVA_HOME
). Está concretamente en el subdirectorio lib/security
y el archivo se denomina cacerts
. Por defecto está protegido por la contraseña changeit
.
Cualquier JRE/JDK incluye en el directorio bin
la herramienta keytool
que permite realizar varias operaciones sobre almacenes de certificados y archivos de certificados. Vamos a ver unas cuantas operaciones con esta herramienta que pueden resultar útiles.
Si tenemos correctamente definida la variable de entorno JAVA_HOME
, podemos utilizarla para referenciar la ruta del truststore predeterminado. Si no está definida o estamos utilizando un JRE/JDK distinto del predeterminado, tendremos que hacer referencia a la ruta que apunte al almacén concreto que queramos gestionar.
Con el parámetro -list
mostramos la lista de certificados contenidos en el truststore predeterminado. Si está protegido por contraseña, debemos indicar la contraseña en el parámetro -storepass
. La ruta al almacén la indicamos con el parámetro -keystore
:
keytool -list -storepass changeit -keystore $JAVA_HOME/lib/security/cacerts
keystore type: jks
Keystore provider: SUN
Your keystore contains 138 entries
actalisauthenticationrootca, Dec 14, 2021, trustedCertEntry,
Certificate fingerprint (SHA-256): 55:92:60:84:EC:96:3A:64:B9:6E:2A:BE:01:CE:0B:A8:6A:64:FB:FE:BC:C7:AA:B5:AF:C1:55:B3:7F:D7:60:66
affirmtrustcommercial, Dec 14, 2021, trustedCertEntry,
Certificate fingerprint (SHA-256): 03:76:AB:1D:54:C5:F9:80:3C:E4:B2:E2:01:A0:EE:7E:EF:7B:57:B6:36:E8:A9:3C:9B:8D:48:60:C9:6F:5F:A7
...
xrampglobalcaroot, Dec 14, 2021, trustedCertEntry,
Certificate fingerprint (SHA-256): CE:CD:DC:90:50:99:D8:DA:DF:C5:B1:D2:09:B7:37:CB:E2:C1:8C:FB:2C:10:C0:FF:0B:CF:0D:32:86:FC:1A:A2
Si al comando anterior le agregamos el parámetro -v
nos mostrará todos los detalles de cada uno de los certificados.
También podemos obtener información detallada de uno de los certificados del almacén en concreto, especificando su alias con el parámetro -alias
. El alias lo habremos visto a raíz del resultado de los comandos anteriores. En el siguiente ejemplo consultamos el detalle del certificado con alias thawteserverca:
keytool -list -v -storepass changeit -keystore $JAVA_HOME/lib/security/cacerts -alias thawteserverca
Nombre de Alias: thawteserverca
Fecha de Creación: 12 feb. 1999
Tipo de Entrada: trustedCertEntry
Propietario: EMAILADDRESS=server-certs@thawte.com, CN=Thawte Server CA, OU=Certification Services Division, O=Thawte Consulting cc, L=Cape Town, ST=Western Cape, C=ZA
Emisor: EMAILADDRESS=server-certs@thawte.com, CN=Thawte Server CA, OU=Certification Services Division, O=Thawte Consulting cc, L=Cape Town, ST=Western Cape, C=ZA
Número de serie: 1
Válido desde: Thu Aug 01 02:00:00 CEST 1996 hasta: Fri Jan 01 00:59:59 CET 2021
Huellas digitales del certificado:
SHA1: 23:E5:94:94:51:95:F2:41:48:03:B4:D5:64:D2:A3:A3:F5:D8:8B:8C
SHA256: B4:41:0B:73:E2:E6:EA:CA:47:FB:C4:2F:8F:A4:01:8A:F4:38:1D:C5:4C:FA:A8:44:50:46:1E:ED:09:45:4D:E9
Nombre del algoritmo de firma: MD5withRSA
Algoritmo de clave pública de asunto: Clave RSA de 1024 bits (débil)
Versión: 3
Extensiones:
#1: ObjectId: 2.5.29.19 Criticality=true
BasicConstraints:[
CA:true
PathLen:2147483647
]
Podemos cambiar la contraseña de un almacén por otra con el parámetro -storepasswd
, indicando la contraseña actual con el parámetro -storepass
y la nueva con el parámetro -new
:
keytool -storepasswd -new nueva_contraseña -keystore $JAVA_HOME/lib/security/cacerts -storepass contraseña_actual
Los certificados digitales se pueden almacenar en diversos formatos. En lo que se refiere a los certificados de clave pública, que son los que se incluyen en un almacén de certificados de confianza para establecer las comunicaciones SSL/TLS, los formatos más habituales son dos:
- PEM. Archivo cifrado en formato Base64. Suele tener extensiones .pem, .cer o .crt, aunque la extensión no es determinante del formato real del archivo. Se distinguen más fácilmente porque su contenido se puede ver claramente con un editor de texto y en él se ven las líneas
----BEGIN CERTIFICATE----
y----END CERTIFICATE----
. En el caso de archivos que contengan una cadena completa de certificación, se puede distinguir el inicio y fin de cada uno de los certificados que contiene gracias a esas etiquetas. - DER. Archivo binario. Suele tener extensiones .der, .crt o incluso .cer. Su contenido no se puede ver de forma “entendible” a través de un editor de textos.
Con keytool
podemos obtener el detalle de un certificado, tanto si está dentro de un almacén como si está en un archivo independiente.
Vamos a ver cómo extraer el detalle de un certificado que viene en un archivo individual. Como ejemplo, vamos a descargarnos el archivo de un certificado raíz de GlobalSign, una de las autoridades de certificación más conocidas. Podemos descargarlo de https://secure.globalsign.net/cacert/root-r6.crt con:
curl https://secure.globalsign.net/cacert/root-r6.crt -o globalsign-root-r6.crt
Podemos mostrar los detalles de este certificado con:
keytool -printcert -file globalsign-root-r6.crt
Los datos de cabecera que nos devuelve el comando son:
Propietario: CN=GlobalSign, O=GlobalSign, OU=GlobalSign Root CA - R6
Emisor: CN=GlobalSign, O=GlobalSign, OU=GlobalSign Root CA - R6
Número de serie: 45e6bb038333c3856548e6ff4551
Válido desde: Wed Dec 10 01:00:00 CET 2014 hasta: Sun Dec 10 01:00:00 CET 2034
Huellas digitales del certificado:
SHA1: 80:94:64:0E:B5:A7:A1:CA:11:9C:1F:DD:D5:9F:81:02:63:A7:FB:D1
SHA256: 2C:AB:EA:FE:37:D0:6C:A2:2A:BA:73:91:C0:03:3D:25:98:29:52:C4:53:64:73:49:76:3A:3A:B5:AD:6C:CF:69
Nombre del algoritmo de firma: SHA384withRSA
Algoritmo de clave pública de asunto: Clave RSA de 4096 bits
Versión: 3
Muestra mucha más información que la del ejemplo anterior, pero nos vamos a centrar en primer bloque de información, concretamente en la línea que nos indica las fechas de validez, que en este caso vemos que van del 10/12/2014 al 10/12/2034. Vamos a procesar el resultado para quedarnos sólo con esta parte de la información, filtrándo por la palabra “Válido”, aunque dependiendo del idioma del sistema puede que tengamos que cambiarla por cualquier otra equivalente:
keytool -printcert -file globalsign-root-r6.crt | grep -E "Válido"
Válido desde: Wed Dec 10 01:00:00 CET 2014 hasta: Sun Dec 10 01:00:00 CET 2034
Ahora modificamos el comando anterior para filtrar con awk
, que por defecto divide en elementos separados por espacio en blanco, especificando las posiciones en las que se encuentran el día, el mes y el año de la fecha de fin de validez:
keytool -printcert -file globalsign-root-r6.crt | grep -E "Válido" | awk '{print $12" "$11" "$15}'
10 Dec 2034
Una vez que tenemos la fecha de fin de validez podemos hacer un pequeño script en bash para calcular los días de validez. Los pásos básicos son:
- Convertir la fecha de fin de validez a un valor unix epoch, que representa el número de segundos transcurridos entre el 1 de enero de 1970 y la fecha.
- Obtener la fecha actual en formato unix epoch
- Obtener la diferencia entre la fecha de fin de validez y la fecha actual, lo que nos dará el número de segundos que faltan hasta la caducidad. Dividimos ese valor entre el número de segundos por día y así obtendremos los días que faltan (o exceden) hasta la fecha de caducidad.
- Mostraremos un mensaje diferente según el certificado esté ya caducado, le falten menos de 30 días para su caducidad o le falten más de 30 días.
Para ello, creamos el archivo check-certificate-expiration
con el siguiente contenido:
#!/usr/bin/env bash
archivo_cert="$1"
finval=$(keytool -printcert -file $archivo_cert | grep -E "Válido" | awk '{print $12" "$11" "$15}')
finval=$(date -d "$finval" '+%s')
hoy="$(date '+%s')"
dias_caducidad=$(( ($finval - $hoy) / (60*60*24) ))
if [ $dias_caducidad -lt 1 ]; then
echo "El certificado ha caducado"
elif [ $dias_caducidad -lt 30 ]; then
echo "Quedan menos de 30 días para que caduque el certificado"
else
echo "Quedan $dias_caducidad dias para que caduque el certificado"
fi
Le damos permisos de ejecución:
chmod +x check-certificate-expiration
Y lo ejecutamos para verificar nuestro certificado
./check-certificate-expiration globalsign-root-r6.crt
Quedan 4689 dias para que caduque el certificado
Con la misma idea, podríamos hacer un script algo más complejo al que le indiquemos como argumento la ruta a un truststore y su contraseña, que compruebe todos los certificados que contiene e informe de aquellos que han caducado o quedan menos de 30 días para que caduquen. Creamos una función validarcertificado
que valida la caducidad de un certificado dentro de un almacén que habremos filtrado por su alias. Una vez que tengamos esa función, obtenemos la lista de todos los alias contenidos en un almacén y llamamos a la función validarcertificado
para cada uno de ellos.
#!/usr/bin/env bash
validarcertificado(){
truststore="$1"
password="$2"
alias="$3"
keytool -list -v -storepass "$password" -keystore "$truststore" -alias "$alias" 2>/dev/null | grep -E "Propietario|Válido" \
| while read propietario ; do
read fechas
propietario=${propietario#"Propietario: "}
finval=$(echo "$fechas" | awk '{print $12" "$11" "$15}')
finval=$(date -d "$finval" '+%s')
hoy="$(date '+%s')"
dias_caducidad=$(( ($finval - $hoy) / (60*60*24) ))
if [ $dias_caducidad -lt 1 ]; then
echo "El certificado $alias ($propietario) ha caducado"
elif [ $dias_caducidad -lt 30 ]; then
echo "Quedan $dias_caducidad para que caduque el certificado $alias ($propietario)"
fi
done
}
truststore="$1"
password="$2"
keytool -list -storepass $password -keystore $truststore 2>/dev/null | grep "trustedCertEntry" \
| while read linea_alias; do
alias=$(echo "$linea_alias" | cut -d ',' -f 1)
validarcertificado "$truststore" "$password" "$alias"
done
Por último podemos verificar la caducidad de todos los certificados que se encuentren en un directorio. Algunos de los archivos de certificados pueden contener más de un certificado, incluyendo toda una cadena, por lo que se incluye la posibilidad de procesar más de un certificado en cada archivo.
Esto se consigue filtrando de la información de cada certificado por las expresiones “Propietario” y “Válido”, con lo que obtendremos todas las líneas en las que aparezca el propietario del certificado y la fecha de validez, en la mayor parte de los casos sólo una de cada tipo. Por la posibilidad de que aparecieran varias, se utiliza la expresión while read propietario
y a continuación se utiliza la instrucción read fechas
que leerá la siguiente línea, que será la que contenga las fechas correspondientes al certificado del propietario obtenido a través del while
. En este ejemplo se buscarán archivos con extensiones .cer
, .pem
y .crt
dentro del directorio especificado:
#!/usr/bin/env bash
validarcertificado(){
archivo_cert="$1"
keytool -printcert -file $archivo_cert 2>/dev/null 2>/dev/null | grep -E "Propietario|Válido" \
| while read propietario ; do
read fechas
propietario=${propietario#"Propietario: "}
finval=$(echo "$fechas" | awk '{print $12" "$11" "$15}')
finval=$(date -d "$finval" '+%s')
hoy="$(date '+%s')"
dias_caducidad=$(( ($finval - $hoy) / (60*60*24) ))
if [ $dias_caducidad -lt 1 ]; then
echo "El certificado ($propietario) de $archivo_cert ha caducado"
elif [ $dias_caducidad -lt 30 ]; then
echo "Quedan menos de 30 días para que caduque el certificado ($propietario) de $archivo_cert"
fi
done
}
directorio=$1
for certificado in $directorio/*.{cer,pem,crt}
do
[ -f "$certificado" ] || continue
validarcertificado "$certificado"
done
Hay casos en los que tenemos que utilizar un almacén específico. Podemos crear un nuevo almacén utilizando el parámetro -genkey
, especificando el algoritmo de clave (parámetro -keyalg
) y el tamaño de clave (parámetro -keysize
). Nos solicitará los datos necesarios de forma interactiva:
keytool -genkey -keyalg RSA -keystore keystore.jks -keysize 2048
Introduzca la contraseña del almacén de claves:
Volver a escribir la contraseña nueva:
¿Cuáles son su nombre y su apellido?
[Unknown]: Dvdcr
¿Cuál es el nombre de su unidad de organización?
[Unknown]: publicaciones
¿Cuál es el nombre de su organización?
[Unknown]: Dvdcr Blog
¿Cuál es el nombre de su ciudad o localidad?
[Unknown]: Madrid
¿Cuál es el nombre de su estado o provincia?
[Unknown]: Madrid
¿Cuál es el código de país de dos letras de la unidad?
[Unknown]: ES
¿Es correcto CN=Dvdcr, OU=publicaciones, O=Dvdcr Blog, L=Madrid, ST=Madrid, C=ES?
[no]: si
Para añadir un certificado de confianza a un almacén existente, utilizamos el parámetro -importcert
. La sintaxis del siguiente ejemplo, especifica todos los datos necesarios para que se haga en un sólo paso y no sea necesario que la herramienta nos pregunte:
keytool -importcert -keystore cacerts18 -storepass "changeit" -noprompt \
-trustcacerts -alias "globalsign-root-r6-custom" -file globalsign-root-r6.crt
Se ha agregado el certificado al almacén de claves
A partir de la información del apartado anterior, podríamos hacer un sencillo script (import-certs-to-store) al que se especifique la ruta al almacén, la ruta al directorio que contiene certificados a importar y la contraseña del almacén y que realice la importación en bloque de todos los certificados que se encuentran en un directorio concreto. Este ejemplo en concreto, busca archivos con extensiones .cer
, .pem
y .crt
. Los importa con un alias que es el nombre del archivo sin extensión ni la barra inclinada de la ruta:
#!/usr/bin/env bash
almacen=$1
dir=$2
password=$3
for certificado in $dir/*.{cer,pem,crt}
do
if [ -f "$certificado" ]; then
alias=${certificado%".cer"}
alias=${alias%".pem"}
alias=${alias%".crt"}
alias=${alias##*"/"}
echo "Importando a [$almacen] certificado de archivo [$certificado] con alias [$alias]..."
keytool -importcert -noprompt -trustcacerts -keystore "$almacen" -storepass "$password" -alias "$alias" -file "$certificado"
fi
done
Ejemplo de ejecución:
./import-certs-to-store ./cacerts ./dir-con-certificados changeit
Importando a [./cacerts] certificado de archivo [./dir-con-certificados/globalsign-root-r6.crt] con alias [globalsign-root-r6]...
Se ha agregado el certificado al almacén de claves
Importando a [./cacerts] certificado de archivo [./dir-con-certificados/ejemplo.pem] con alias [ejemplo]...
Se ha agregado el certificado al almacén de claves
Para que una aplicación Java utilice un trustStore específico diferente del que incluye por defecto el JDK/JRE sobre el que se ejecuta, hay que indicar la ubicación de dicho truststore con la variable javax.net.ssl.trustStore
y la contraseña de dicho almacén a través de la variable javax.net.ssl.trustStorePassword
.
java -Djavax.net.ssl.trustStore=/opt/java-alt/JRE/lib/security/cacerts \
-Djavax.net.ssl.trustStorePassword="contraseña" \
-jar aplicacion.jar
Icono de caja fuerte creado por Diego Marquetti