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

 

Almacén de certificados de confianza predeterminado en Java

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.

 

Herramienta de gestión de certificados

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.

 

Listar certificados contenidos en un truststore

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
]

 

Cambiar contraseña de un almacén de certificados

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

 

Archivos de certificados

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.

 

Comprobación de caducidad de certificados

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:

  1. 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.
  2. Obtener la fecha actual en formato unix epoch
  3. 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.
  4. 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

 

Crear un almacén de tipo jks en modo interactivo

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

 

Añadir un certificado de confianza a un almacén

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ñadir todos los certificados contenidos en un directorio a un almacén

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

 

Especificar truststore para aplicaciones Java

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

 

Referencias

 

Atribución

Icono de caja fuerte creado por Diego Marquetti