Cómo funciona la firma digital de actas con Izenpe

Desde hace muuucho tiempo he querido saber cómo funciona internamente la firma digital de actas de mi universidad (EHU). Los que me conocéis ya sabéis que cuando digo «internamente» es realmente a nivel de disección. ¿Me acompañas en el viaje? Vamos allá….

actas1

Las actas de exámenes, trabajos fin de grado, etc. se firman a través de GAUR. Te identificas como profesor, entras en la sección de Exámenes / Proceso de Firma y ahí tendrás los PDF -que previamente has calificado- listos para firmar.

Cuando pulsas en Firmar uno de los PDF se ejecutará la siguiente función JS: onclick=»a_firmar(id_pdf,id_firmante,id_version); Esos valores han sido calculados para esta sesión del usuario (del profesor). La función a_firmar llama a su vez a abrir_dialog() que lanza una URL https://gestion.ehu.es/idazki/index?query_string siguiendo este patrón para los parámetros de la query_string:

Application ID: 3 Language: CAS (Castilian/Spanish) Action: 6 Session ID: 939c91xxxxxxxxxxxxxxxxxxxxxxxxxx3837694 PDF ID: YYYYYYYY Signer ID: ZZZZZZZZZ Document number: AAAAAAA Version: 1 Timestamp: 8,98107480305963801233649306596140178816

Siguiendo esa URL, veremos lo siguiente:

actas2

En el código fuente de esa URL veremos que el PDF a firmar está codificado en base64 dentro de un JS. Si ahora pulsamos en «Firmar», se inicia el proceso de firma digital.

El browser llamará a Idazki Desktop (una aplicación residente de Izenpe, que intercepta las URL que comienzan con el protocolo idazki://). Este script del navegador pasará el PDF a firmar (en base64) a Idazki Desktop para que el usuario (el profesor) seleccione el certificado con el que quiere firmarlo. Una vez firmado, Idazki Desktop devolverá el control al navegador. Este comprobará que todo está OK y enviará el acta firmada a la EHU. Pero hemos dicho que queremos diseccionar, así que hay que meterse en harina… ok, no te aburriré con los detalles en este post, pero puedes verlos aquí si te pica la curiosidad 🙂

Para confirmar que entendí correctamente el funcionamiento del proceso se me ocurrió crear una pequeña web de prueba: https://ikasten.io/idazki.html

actas3

La idea es que puedas subir un PDF cualquiera (te dará la opción para seleccionarlo de tu disco duro) para que mi script lance la aplicación Idazki Desktop y puedas firmar el PDF con tu certificado digital. Si la firma se completa, te dará un bonito mensaje de éxito. Si no, te informará del error.

P: ¿No necesito identificarme? R: No. Idazki Desktop solo recibirá un PDF y una orden de firmarlo digitalmente. Ahí sí, verás tu lista de certificados digitales. Selecciona el tuyo, introduce el PIN y la aplicación Idazki firmará el PDF, devolviendo el control al navegador.

P: ¿No estarás guardando el PDF en tu servidor? R: No. El navegador carga el PDF en un blob base64 en local. No se envía nada al servidor. No tienes por qué creerme, abre el código fuente de idazki.html y lo verás.

Abre la URL de prueba: https://ikasten.io/idazki.html

Selecciona un PDF. Pulsa en Sign PDF. El navegador debe abrir Idazki Desktop.

actas4

Idazki Desktop debe abrir una ventana «nativa» (es una app Java usando Swing, el look&feel le delata) para que selecciones el certificado con el que quieres firmar. Pulsas Aceptar y te pedirá el PIN o la clave asociada. Lo introduces y …..

actas5

¡Bum! Si estás en macOS con un procesador ARM fallará 🙂

actas6

Con este mensaje de error:

actas7

Pero eso es otra larga historia que os contaré otro día (por qué falla, cómo encontrar el bug y cómo arreglarlo SIN tener acceso al código fuente). Una bonita historia que explica cómo parchear un binario Java del que no tienes el código fuente, usando IA por el camino.

actas8
Pero si usas un sistema operativo + procesador soportado (o el parche del que os hablo si usas macOS+ARM), todo irá bien:

actas9

Addendum:

¿Y podría bajarme el PDF firmado? Yep. Si quieres tener esa opción, he preparado otro script de prueba más completo aquí: https://ikasten.io/idazkiFull.html

actas10

Una vez firmado el PDF, pulsa en «Download Signed PDF» y podrás descargártelo. Si lo abres con alguna aplicación que soporte la visualización de firmas en PDF, como Adobe Acrobat Reader, podrás verla:

actas11

HackIt!2013: Level 2 (y II)

Según esta web, la contraseña para acceder a un repo Maven puede ser generada a partir de una contraseña maestra así:

$ mvn --encrypt-master-password Oone3vei
{wsJL3n5FpasHjLctHj2HuHIoc8DBGtpIWp2bc40vkBU=}
$ mvn --encrypt-password Thu8luuV
{uD995k4e9YEHeRC0LWz4jIEv/kAt5Mt/up3X62RoJIs=}

Es decir, si la contraseña maestra fuera Oone3vei, se generaría esto: {wsJL3n5FpasHjLctHj2HuHIoc8DBGtpIWp2bc40vkBU=}, lo que deberíamos guardar en el fichero settings-security.xml. A partir de esa contraseña maestra, podremos generar otras (¿para distintos repos Maven?¿para distintos usuarios del mismo repo?). En el ejemplo, tomando el password Thu8luuV, y a partir de la contraseña maestra, se generará el pass {uD995k4e9YEHeRC0LWz4jIEv/kAt5Mt/up3X62RoJIs=}, que se guardará en setttings.xml.

Teniendo ambos ficheros, ¿es posible obtener el string que se usó como password y que generó el pass cifrado de settings.xml? Sí, usando el siguiente código Java:

import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
import org.sonatype.plexus.components.cipher.PlexusCipher;
import org.sonatype.plexus.components.cipher.PlexusCipherException;
 
public class Prueba {
 
	public static void main(String[] args) {
		PlexusCipher cipher;
		try {
			cipher = new DefaultPlexusCipher();
			String masterPw = cipher.decrypt("CO0lvhBKZAMHPlguhfnJAWS6zgpLe5BoQO/AwhVwJX/4UEPxkeqBjVKAq+yK37ft", "settings.security");
		String appPw = cipher.decrypt("9FANUCx4GboHlC12nghO/i+oVV4RRSw1grsm6or+KiYJ2tSEAG5BSWAgq1QCmejj9Q4kpppWwU8caX2PioJD1w==", masterPw);
		System.out.println(appPw);
 
	} catch (PlexusCipherException e) {
		e.printStackTrace();
	} 
	}
 
}

Por cierto, necesitarás instalar los paquetes .jar de Plexus Cipher para poder compilar ese código:

$ sudo apt-get install libplexus-cipher-java

Hackit’2012, solucionario. Level 3.

Descargamos el fichero enlazado y vemos que es un zip con ficheros binarios .class (Java) dentro:

$ unzip jkhil.zip 
Archive:  jkhil.zip
   creating: org/
   creating: org/euskal/
   creating: org/euskal/hackit/
   creating: org/euskal/hackit/crypt/
  inflating: org/euskal/hackit/crypt/CryptUtil.class  
  inflating: org/euskal/hackit/PasswordRevealer.class  
  inflating: org/euskal/hackit/FileClassLoader.class  
  inflating: org/euskal/hackit/trololo.clazz

Vamos a ejecutar el que más llama la atención:

$ java org.euskal.hackit.PasswordRevealer
Exception in thread "main" java.lang.VerifyError: Bad local variable type in method org.euskal.hackit.trololo.damagedMethod()I at offset 7
	at java.lang.Class.getDeclaredConstructors0(Native Method)
	at java.lang.Class.privateGetDeclaredConstructors(Class.java:2404)
	at java.lang.Class.getConstructor0(Class.java:2714)
	at java.lang.Class.newInstance0(Class.java:343)
	at java.lang.Class.newInstance(Class.java:325)
	at org.euskal.hackit.PasswordRevealer.main(PasswordRevealer.java:23)

¡Anda!, un método que se llama damagedMethod()… con un tipo de datos de variable local incorrecto (menudo binario nos ha «regalado» cymo, el autor de este level 😉

Curiosamente, al ejecutar el .class anterior se ha creado el fichero trololo.clazz.dec (deciphered?) :

$ ls -alt org/euskal/hackit/
total 28
drwxr-xr-x 3 juanan juanan 4096 Sep 18 00:24 .
-rw-rw-r-- 1 juanan juanan 2320 Sep 18 00:24 trololo.clazz.dec
-rw-r--r-- 1 juanan juanan 1672 Jul 19 19:04 PasswordRevealer.class
-rw-r--r-- 1 juanan juanan 3101 Jul 19 18:57 FileClassLoader.class
-rw-r--r-- 1 juanan juanan 2320 Jul 19 18:53 trololo.clazz
drwxr-xr-x 2 juanan juanan 4096 Jul 19 14:53 crypt
drwxr-xr-x 3 juanan juanan 4096 Jul 19 13:59 ..

Antes de seguir investigando ese fichero, ¿qué ocurriría si le decimos a la máquina virtual Java que no verifique el bytecode del .class anterior? (la idea sería saltarse la excepción java.lang.VerifyError de algún modo):

$ java -noverify org.euskal.hackit.PasswordRevealer
Exception in thread "main" java.lang.SecurityException: Can't run unmannaged code. System error: 0x1337
    at org.euskal.hackit.PasswordRevealer.main(PasswordRevealer.java:20)

Pues no le ha gustado…

Ahora no sé si el camino que seguimos en mi grupo (kudos to @ochoto por ser el que rompió este nivel) es el que el autor del level pensó, pero a nosotros nos funcionó O:-)

Estos son los pasos: renombrar el trololo.clazz.dec a trololo.class y crear una clase que lo use:

 
// guardarlo en un fichero dl.java en /tmp
// compilarlo con javac dl.java
 
import org.euskal.hackit.trololo;
 
public class dl {
	public static void main(String args[]) {
		try {
			trololo t = new trololo();
 
		}
		catch(java.lang.VerifyError ve) {
                          System.err.println("Errooooooor....");
		}
	}
}

Si ejecutamos el main anterior, veremos que salta la excepcion «VerifyError». Por lo que vamos a volver a intentar la ejecución sin verificación del bytecode con la opción -noverify (ver documentación de esta opción en la especificación oficial de la máquina virtual Java):

$ cd /tmp
$ java -noverify dl
Mr. Trololo was born ...
Please enter: Year: >

Premio! 🙂 Buscamos los datos que nos piden y tendremos acceso al siguiente nivel. Si algún grupo (o el autor del level) siguió otro camino para resolver, le agradecería que añadiera sus comentarios. Nos vemos en el level 4.

Tip: ¿tu versión de Java está actualizada?

Tienes la versión 1.6.0_22 del JRE de Java. ¿Es la última? ¿Cómo saberlo? Fácil, sólo tienes que visitar esta página de java.com. Un applet Java detectará tu versión y te indicará si estás trabajando con la última versión o no. En caso negativo te dará un enlace para descargar las versión más reciente (en este caso la 1.6.0_23)

Sinadura 1.3 publica el código fuente y manual técnico

David Olmos (Zylk.net) me envió a finales de año información sobre el código fuente de la versión 1.3 de Sinadura, la aplicación libre para el uso de firma digital en Linux (en concreto, para la firma digital de documentos PDF).

«Acabamos de publicar las fuentes de la versión 1.3 de sinadura, está dividido en en core y en desktop como en la v.1.0. Hemos estado limpiándolas un poco y, todavía no tenemos manual para explicar cómo se configura el proyecto para compilar desde las fuentes y satisfacer las dependencias, pero dános unas semanas y estará todo publicado.»

Dicho y hecho, han pasado unas semanas y ya tenemos manual técnico, que nos explica paso a paso cómo descargar y compilas las fuentes de Sinadura. Entre las funcionalidades que se requieren con más urgencia yo destacaría el soporte a la comprobación de listas de revocación (si no se comprueba no podremos realmente confirmar la validez de un certificado) y el soporte a la firma múltiple (imprescindible para firmar actas con certificado digital por parte de varias personas).

Igual hasta se podrían proponer estas funcionalidades al Google Summer Of Code 2009… si es que este año Google también se anima a llevarlo adelante (con eso de la crisis, lo veo difícil…)