miércoles, 20 de mayo de 2009

Ejecutando únicamente código firmado

En algunos casos puede ser necesario impedir la ejecución de código no firmado por motivos de seguridad.

Java proporciona un mecanismo bastante sencillo para establecer este comportamiento, el policy file.

Para que se comprueben los permisos establecidos en ese fichero, es necesario que un gestor de seguridad los aplique. Este gestor de seguridad o SecurityManager, puede ser cargado desde código o en este caso más cómodamente como opciones de máquina virtual:

-Djava.security.manager

El fichero por defecto de la JVM es ${java.home}/lib/security/java.policy donde asignaremos los permisos deseados o ninguno:

// Standard extensions get all permissions by default

grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};

// default permissions granted to all domains

grant {
};

Seguidamente asignaremos los permisos necesarios para nuestro código firmado, en este caso, todos:

grant SignedBy "jordi" {
permission java.security.AllPermission;
};

Es muy importante modificar un par de propiedades del fichero ${java.home}/lib/security/java.security para impedir establecer un policy file distinto o cambiar los valores establecidos en java.security:

policy.allowSystemProperty=false
security.overridePropertiesFile=false

En el siguiente ejemplo, se intenta obtener una propiedad del sistema para la que no tenemos permisos, incluso se intenta establecer un policy file que da privilegios absolutos pero el gestor nos lo impide:

public class Simple{
public static void main(String[] ar){
System.out.println(System.getProperty("java.vm.name"));
}
}

El resultado es:

java -Djava.security.manager -Djava.security.policy=java.policy Simple
Exception in thread "main" java.security.AccessControlException: access denied (java.util.PropertyPermission java.vm.name read) at java.security.AccessControlContext.checkPermission(AccessControlContext.java:323)
at java.security.AccessController.checkPermission(AccessController.java:546)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1285)
at java.lang.System.getProperty(System.java:652)
at Simple.main(Simple.java:3)

Los pasos a seguir para crear y firmar el JAR son:

jar cvf Simple.jar Simple.class
keytool -genkey -alias jordi -keypass xxxxxxx -keystore test -storepass xxxxxxx
jarsigner -keystore test -signedjar SimpleSigned.jar Simple.jar jordi

Si se quiere ejecutar el JAR en una máquina distinta, deberemos extraer el certificado y mandarlo para que sea importado:

keytool -export -keystore test -alias jordi -file jordi.cer
keytool -import -alias jordi -file jordi.cer

En el policy file podemos especificar el keystore donde hemos importado el certificado, quedando así:

keystore "file://c:/.keystore", "jks";

grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};

grant {
permission java.util.PropertyPermission "java.vm.vendor", "read";
};
grant SignedBy "jordi" {
permission java.security.AllPermission;
};


Si ahora ejecutamos nuestro programa: (he modificado el manifest.mf para establecer la clase principal a Simple)

java -Djava.security.manager -jar SimpleSigned.jar
Java HotSpot(TM) Client VM

Sé de alguien que me va a invitar a comer ;-)
Hasta pronto!

7 comentarios:

Cancrillo dijo...

Hola Jordi,

Primero de todo, excelente la explicación.

Quisiera saber, si eres tan amable, cómo sería un fichero java.policy "por defecto" seguro, es decir, lo mínimo que debería contener para considerar segura la ejecución de una aplicación sobre la JVM.

Y una segunda cuestión: Si ya tenemos una instancia de SecurityManager ejecutándose (un administrador ha ejecutado una que considera segura), ¿es posible que una aplicación la suplante por otra con intención de crear agujeros de seguridad?

Muchas gracias por adelantado.

Un saludo.

Antonio.

JavaExperto dijo...

Hola Antonio,

Muchas gracias por escribir.

La primera pregunta tiene dificil respuesta. El policy.file deberías configurarlo como si fuera un firewall y solo conceder los privilegios imprescindibles.

La segunda. No, excepto si el policy.file lo permite:

permission java.lang.RuntimePermission "setSecurityManager";

Saludos,

Jordi

Cancrillo dijo...

Hola de nuevo Jordi,

Siento molestarte otra vez, pero estoy super atorado con una historia y no sé a quién recurrir.

¿Qué ocurre si no especifico en el java.policy ningún permiso específico? Es decir, yo no sé a priori qué aplicaciones se van a ejecutar sobre mi JVM, por lo que no sé cómo debería ser mi java.policy por defecto, pero necesito tener uno, lo más restrictivo posible. ¿Qué me recomiendas?

Muchas gracias por tu atención y rapidez en tu respuesta, de veras. Siento de nuevo la molestia.

Un cordial saludo.

Antonio.

JavaExperto dijo...

Hola de nuevo Antonio :-)

Entiendo tu estado. Es una situación bastante compleja porque no hay una solución buena.
Por defecto, si tienes un SecurityManager activado y tu policy file no contiene permisos se va a denegar cualquier petición que requiera cualquier pemiso. Es la opción más segura posible, por contra el riesgo que las aplicaciones fallen es grande.

Siento no poder darte una respuesta que te sirva :(

Saludos

Cancrillo dijo...

Hola Jordi,

Prometo que es la última que te hago ;)

¿Si dejo el fichero java.policy vacío, sin ninguna línea, se supone que por defecto no hay permisos para nada? Al instalar el jre1.6u18, he visto que el policy es bastante inseguro (para empezar da AllPermissions). Si elimino todo eso, ¿tendré una JVM que no otorgará permisos especiales a ninguna aplicación? ¿Dónde puedo mirar los valores por defecto de las distintas opciones de permisos que hay?

Disculpa la lata que te estoy dando. Muchísimas gracias por tu atención y concreción en las respuestas.

Un saludo.

JavaExperto dijo...

Hola :)

Espero no decir nada raro, estoy medio dormido ya :)

Si está vacio, no das permisos a nada, exacto.

Por defecto no es inseguro, asigna todos los permisos a las librerias de extensiones (java.ext.dirs). Puedes comprobar su valor actual desde código con:

System.getProperty("java.ext.dirs")

Por otro lado, te recomiendo que juegues un poco, por ejemplo, con el policy por defecto esto te va a lanzar una excepción:

public static void main(String[] args) throws FileNotFoundException{
System.setSecurityManager(new RMISecurityManager());
FileInputStream fis = new FileInputStream("c://autoexec.bat");

}

Puedes encontrar documentación de los permisos en la web de sun (los buscaste?):

http://java.sun.com/javase/6/docs/technotes/guides/security/permissions.html

Saludos,

Jordi

Cancrillo dijo...

Jejejejeje, perfecta y concisa la aclaración... a pesar de la hora ;)

Muchas gracias por todo. Ahora tengo una perspectiva un poco más clara de todo esto. La página que me indicas de sun sí que la miré, pero al no ver valores por defecto ni ser una explicación muy clara, pensé que habría algo por ahí más "explícito", por así decirlo.

Agradecerte nuevamente tu atención.

Un saludo!