 |
Keystore de CAs en Java |
Los programas en Java pueden tomar forma de ficheros .jar,
.apk... según se usen para ejecutables, applets, programas para Android...
En
realidad no son más que zips con un formato muy característico. Los ficheros de este tipo firmados, además,
contienen en su interior una serie de archivos que permiten garantizar su
integridad y, en cierta forma, su procedencia gracias a un certificado. Los
certificados de los programas Java firmados, se validan en un
"keyring" de CAs que viene de serie con la instalación de Java, y que
es independiente al de Windows o del navegador Firefox, por ejemplo. Los
applets, sin embargo, sí que se pueden "validar" contra el keystore
de Windows.
En general, la comprobación de la firma se realiza
"normalmente" con la misma aplicación del SDK de Java que permite
firmarlos (jarsigner), solo que modificando la opción. Por ejemplo:
 |
Ejemplo de validación de firma de un APK |
Lo que ofrece información sobre ficheros, la verificación,
etc... pero ciertamente es una información muy pobre y peor presentada
visualmente. ¿Qué está ocurriendo internamente? En realidad, varias
comprobaciones diferentes. Toda la información relativa a la firma e
integridad se almacena dentro, en el directorio
META-INF del ZIP. Ahí se pueden encontrar varios ficheros. Vayamos por
partes.
Manifest.mf
Es un fichero donde se incluye el hash (normalmente SHA1) de
cada fichero contenido en el ZIP. El hash se codifica en base64. El
formato es simple: primero unas cabeceras, y luego "Name" y SHA1-Digest
(que puede ser SHA256-Digest). Por ejemplo, AfPh3OJoypH966MludSW6f1RHg4=
, decodificado en hexadecimal
da como resultado 01f3e1dce268ca91fdeba325b9d496e9fd511e0e que es el
SHA1 de
ese archivo.
Abajo, en la imagen de la izquierda se observa un ejemplo real de manifiesto.
Un fichero Signature File (extensión SF)
También en el directorio META-INF, se encontrará un fichero con
cualquier nombre,
pero de extensión SF (signature file). En él se observan de nuevo unas
cabeceras, y un formato muy parecido al del manifiesto, con lo que se
diría que es el SHA1 de cada fichero... otra vez. Pero curiosamente, diferente al valor que se muestra en el manifiesto. En realidad, el hash que aparece el el signature file, no es el hash del fichero, sino de las tres líneas del manifiesto donde se muestra el hash.
Si se almacenan tal cual las tres líneas (Name, SHA1-Digest y una en
blanco) en un archivo de texto, se calcula su SHA1 y se codifica en
base64, el resultado sería el que aparece en el signature file.
 |
Fichero manifiesto a la izquierda y "signature file" a la derecha de un mismo APK. |
En la cabecera del signature file, a través de SHA1-Digest-Manifest, se calcula al SHA1 del fichero del manifiesto. Efectivamente, en el ejemplo, Og+1qY7DjNHiykvwIOijpOUYPBI= coincide con el SHA1 en hexadecimal de archivo de manifiesto...
Como el manifiesto a su vez, contiene el hash del resto de archivos, estamos de
esta forma "validando" a todos los ficheros internos de una vez.
Comprobando el hash del manifiesto (en el signature file), de alguna manera,
comprobamos el resto de archivos. En esta imagen de ejemplo, se resume todo.
El certificado
Lo explicado hasta ahora cubre la integridad, pero no se ha hablado de la criptografía pública. ¿En
qué punto entra el certificado? Finalmente, en el directorio META-INF se puede
encontrar un fichero con extensión DSA o RSA, según se use uno u otro algoritmo
de firma digital.
Lo que se firmará con la clave pública del certificado, es
el hash del fichero "signature file". Así se "cierra el
círculo". El manifiesto mantiene un listado de todos los ficheros internos
y su integridad, el "signature file", mantiene la integridad del
manifiesto. Y el signature file es firmado con una clave pública.
La verificación
La verificación se puede observar en el propio código fuente de Java o OpenJDK
o en su documentación. Resumiendo, se hace en varios pasos:
1) Se verifica la firma asímétrica del signature file (extensión .SF). Se verifica la firma RSA o DSA
contra el certificado.
2) Si existe el valor "SHA1-Digest-Manifest" (u otro SHA) en el fichero SF, se comprueba que es el
correcto. Con esto se asegura que el manifiesto no ha sido alterado. Si es correcto, se elude el paso siguiente (y se pasa al 4).
3) Si esa cabecera no existe, o su valor no es correcto (se asume que se
ha modificado el hash del manifiesto), no se considera inválido el
archivo jar o apk directamente. En realidad, se hacen dos cosas:
- a) Si existe la
cabecera Digest-Manifest-Main-Attributes en el fichero SF, se calcula el hash de los atributos
principales del manifiesto. Los "atributos principales" son esos primeros datos
que aparecen en el fichero y que no corresponden propiamente a cada archivo
interno. Si no es válido, la comprobación falla.
- b) Si no existe esa cabecera, se
comprueba una a una cada entrada en el fichero SF. O sea, que el SHA1 corresponde a
esas tres líneas del manifiesto. Con esto gana velocidad y eficiencia, puesto
que no tiene que calcular el hash de cada fichero, sino que se fía de que el
hash ya calculado en el momento de compilación y contenido en el manifiesto, no
haya sido alterado. Si algo falla, la firma falla.
4) En el cuarto paso,
se
comprueba cada fichero, su hash real, contra el calculado en el manifiesto.
El modelo en general es
bastante curioso. Se puede dar el caso en el que difiera el hash real del manifiesto con el hash del manifiesto almacenado en el
fichero de firmas... pero que eso no signifique que la firma no sea válida. Y
esto es
así porque se pueden añadir ficheros a un .jar, sin que se altere su
firma. De
hecho, si se usa la herramienta "jar" para añadir ficheros al programa,
cambiará el manifiesto,
pero no el fichero de firmas. Así que su firma con el certificado no se
ve
alterada y seguirá siendo válido la firma digital, aunque el ZIP en sí
sea completamente diferente. Si se da el caso, para alertar de este
hecho, durante la verificación simplemente se mostrará un warning de
este tipo:
Warning:
This jar contains
unsigned entries which have not been integrity-checked.
Como siempre, una firma válida habla de la integridad (y posible procedencia, según el certificado) de la aplicación, pero nada de sus intenciones sobre el sistema...
Sergio de los Santos
ssantos@11paths.com