15.4.14

Uso de jarsigner con un Provider propio

En el último post expliqué cómo usar un Provider propio para autenticarse con certificado de cliente en una conexión HTTPS. El siguiente paso era inevitable: queríamos usar la misma infraestructura para firmar ficheros jar (requisito indispensable si queremos desplegar un applet con ciertos privilegios, por ejemplo).

Como sabéis, el JDK nos ofrece una herramienta de línea de comandos llamada jarsigner. Es la única opción que tenemos para firmar un jar, ya que no se ofrece un API para poder hacerlo de forma programática. La herramienta está pensada sobre todo para utilizar un keystore Java o un fichero PKCS#12, donde estaría la clave privada. Pero ¿qué hacemos si la única forma de acceder a la misma es mediante un Provider propio.

Nuevamente la solución pasa por estudiar primero cómo se haría con un PKCS#11. La documentación oficial de Oracle es un poco parca al respecto, pero suficiente para hacernos una idea. Al igual que ocurría en el post anterior, si nuestra implementación no está basada en un fichero en el disco donde se encuentran las entradas, como opción -keystore se debe pasar el literal "NONE". La opción -storetype deberá tener el nombre que le hayamos dado a nuestro propio tipo de KeyStore. Y aquí viene lo importante: deberemos usar la opción -providerClass con el nombre completo de nuestra implementación de Provider, incluyendo el paquete. Un ejemplo sencillo podría ser el siguiente:


jarsigner -keystore NONE -storetype MyType -storepass password -providerClass my.package.MyProvider /path/to/app.jar alias

Pero para que nos funcione, antes debemos hacer algo muy importante. ¿Cómo sabe la herramienta jarsigner dónde está el jar con nuestra implementación de Provider? Pues en realidad, no lo sabe. Ni tampoco se lo podemos decir. La herramienta jarsigner no tiene una opción -classpath o similar con que pasarle las rutas con los jars que debe usar. Así que nuestra única posibilidad es añadir nuestra implementación a la instalación del JDK, en la carpeta destinada a extender el JRE: $JAVA_HOME/jre/lib/ext/. No hay que perder de vista que tal vez necesitemos permisos de administrador para ello.

Además, hay tener en cuenta las implicaciones de esta acción: cualquier aplicación Java que se ejecute con esa instalación, podrá usar nuestra implementación de Provider. Eso no quiere decir que nuestra implementación pueda usarse por accidente si no queremos. Añadir nuestros jars a la carpeta de extensión, sólo quiere decir que sus clases estarán disponibles, como si se trataran de las del propio JRE. Pero si una aplicación quiere hacer uso de nuestro Provider, necesitará instalarlo con la conocida llamada a java.security.Security.addProvider(java.security.Provider provider). Si queremos que nuestro Provider esté siempre disponible como el resto de los que trae el JDK, sin necesidad de añadirlo dinámicamente en nuestra aplicación, debemos configurarlo en el fichero $JAVA_HOME/jre/lib/security/java.security (y hay que estar muy seguros de que realmente es eso lo que queremos).

Posiblemente, nuestra implementación necesite algún parámetro. En mi caso concreto, era la URL del servicio remoto. En el caso de la implementación PKCS#11 de Sun, es un fichero de configuración. Si a jarsigner le pasamos la opción -providerArg, el JDK buscará un constructor con un String como único argumento, y lo invocará usando el valor de la opción en cuestión.

Otro detalle muy importante, que nos puede dar sorpresas si no lo tenemos en cuenta. La herramienta usa una serie de algoritmos de firma por defecto, dependiendo del tipo de nuestra clave privada. Por ejemplo, para una clave RSA, el algoritmo por defecto es SHA256withRSA a partir de Java 7. Si nuestro Provider no implementa el algoritmo que jarsigner elija, se utilizará otro Provider, con resultados no deseados. Así que, o bien nos aseguramos de que nuestro Provider implemente los algoritmos por defecto, o bien utilizamos la opción -sigalg, indicando el algoritmo que queremos usar.

Así que nuestra llamada a jarsigner nos podría quedar algo parecido a esto (por claridad, he troceado el comando en varias líneas):


$HAVA_HOME/bin/jarsigner \
  -keystore NONE \
  -storetype MyKeyStoreType \
  -storepass mypassword \
  -providerClass my.own.package.MyProvider \
  -providerArg "some string with some configuration" \
  -sigalg SHA1withRSA \
  /path/to/file/to/be/signed/app.jar \
  alias-to-use