Cómo extraer los certificados raíz de las autoridades de certificación de un Mozilla Firefox

¿De dónde sacamos un listado de certificados raíz de autoridades de certificación X.509?. A veces necesitamos obtener esos certificados para, por ejemplo, instalarlos en un entorno empotrado que no los gestiona automáticamente.

Podemos sacar esos certificados raíz de un Linux actualizado pero hay una opción interesante: Mozilla ya se preocupa de mantener esos certificados actualizados y nuestra instalación de Firefox puede contener certificados adicionales que nos interesan, como certificados autofirmados de confianza o certificados corporativos privados. Las entidades de certificación presentes en el Firefox que usamos habitualmente pueden ser más útiles que los del propio sistema operativo.

En mi artículo anterior, Python 2.7.9 y Python 3.4.3 pasan a verificar las conexiones HTTPS por defecto, no comenté un caso real adicional: entorno empotrado desarrollado a medida que quiere comunicarse por HTTPS de forma segura.

Veamos qué hice.

En mi caso podría haber copiado los certificados reconocidos de mi sistema operativo pero mi instalación personal de Firefox es más fiable: he eliminado certificados (¿qué pinta el certificado raíz de la oficina postal de Hong Kong o de una entidad turca en mi navegador personal?) y he añadido certificados que me parecen importantes o que, sencillamente, necesito yo personalmente.

Buscando un poco por Internet veo que se trata de un problema resuelto. El proyecto cURL genera periódicamente un listado de certificados raíz. No me gusta fiarme de un fichero opaco tan crítico que no controlo pero esta gente ha publicado un script para generarte ese fichero opaco tan delicado a partir de tu propio Firefox (se puede hacer lo mismo con Thunderbird, si lo prefieres). Puede estudiar este script, que es muy sencillo, y asegurarte de que hace lo que dice hacer.

Problema resuelto.

Bueno, no. Esta herramienta me genera un fichero con todos los certificados raíz juntos. A OpenSSL le serviría, pero mi entorno empotrado va justo de memoria y CPU y prefiero usar un fichero por certificado en vez de almacenarlos todos juntos en un mismo fichero. Así solo se carga el certificado raíz que necesitamos en cada conexión, no los cientos posibles.

Mi script se basa en la versión de cURL pero genera un fichero por certificado raíz. Su nombre es su hash:

 #!/bin/sh

 # 2015 jcea@jcea.es - https://www.jcea.es/
 # Código liberado como Dominio Público.
 # Haz con él lo que quieras.

 db=`ls -1d $HOME/.mozilla/firefox/*default`
 currentdate=`date`
 certutil -L -h 'Builtin Object Token' -d $db | \
 grep ' *[CcGTPpu]*,[CcGTPpu]*,[CcGTPpu]* *$' | \
 sed -e 's/ *[CcGTPpu]*,[CcGTPpu]*,[CcGTPpu]* *$//' -e 's/\(.*\)/"\1"/' | \
 while read nickname ; \
 do
     echo $nickname
     echo $nickname > .tmp
     echo $currentdate >> .tmp
     echo >> .tmp
     eval certutil -d $db -L -n "$nickname" -a >> .tmp
     name=`openssl x509 -noout -hash -in .tmp`
     hash=`tail -n +3 .tmp | sha256sum | cut -c1-64`
     v=0
     while true ;
     do
         if [ -f $name.$v ]
         then
             hash2=`tail -n +3 $name.$v | sha256sum | cut -c1-64`
             if [ "$hash" = "$hash2" ]
             then
                 break
             else
                 v=`expr $v + 1`
             fi
         else
             mv .tmp $name.$v
             break
         fi
     done
 done

La línea 7 localiza nuestra instalación Firefox por defecto (podría ser Thunderbird). Las líneas 9-11 extraen la lista de certificados en los que confiamos.

A continuación iteramos sobre esa lista. Creamos un fichero temporal con el nombre del certificado y la fecha de exportación (líneas 14-18). Determinamos el nombre con el que lo va a buscar la librería TLS (líne 19) [1] y vemos si ese nombre ya está ocupado (línea 24). Si lo está, comprobamos si se trata del mismo fichero (líneas 20 y 26-27). Si es así, lo ignoramos. En caso contrario seguimos buscando una variación del nombre que esté libre (línea 31).

El resultado es grabar en disco un conjunto de ficheros con los nombres apropiados para que la librería TLS pueda cargar exclusivamente la clave pública asociada a un certificado X.509 concreto recibido en una conexión HTTPS.

Como he dicho antes podemos extraer los certificados raíz tanto de una instalación Firefox como de una instalación Thunderbird. Lo que necesites.

[1]

Un mismo nombre puede tener asociados certificados diferentes, pero en los experimentos que he hecho con OpenSSL siempre muestra el mismo resultado para el comando:

openssl x509 -noout -hash -in CERTIFICADO.CRT

No sé qué campos se están considerando exactamente para generar ese hash, tal vez simplemente el hash del nombre del dominio, pero parece coincidir para distintos certificados para el mismo dominio.

Habría que verificarlo viendo el código fuente de la librería TLS. Si hubiese casos en los que esto no es así tendríamos un problema porque estaríamos perdiendo autoridades de certificación en el proceso de exportación. El script de cURL no tiene este problema porque exporta todo a un mismo fichero sin preocuparse de si hay duplicados.