No es un bug, es una característica no documentada

24/3/16

Procesos y servicios. Programación segura (III). Seguridad en entornos Java

Antes de que la JVM comience el proceso de interpretación y ejecución de los bytecodes (código objeto) debe realizar una serie de tareas para preparar el entorno en el que el programa se ejecutará. Este es el punto en el que se implementa la seguridad interna de Java.

Hay tres componentes en el proceso:
  • El cargador de clases
  • El verificador de ficheros de clases
  • El gestor de seguridad

El cargador de clases

Es el responsable de encontrar y cargar los bytecodes que definen las clases. Cada programa Java tiene como mínimo tres cargadores:
  • El cargador de clases bootstrap que carga las clases del sistema (normalmente desde el fichero JAR rt.jar)
  • El cargador de clases de extensión que carga una extensión estándar desde el directorio jre/lib/ext
  • El cargador de clases de la aplicación que localiza las clases y los ficheros JAR/ZIP de la ruta de acceso a las clases (según está establecido por la variable de entorno CLASSPATH o por la opción –classpath de la línea de comandos)

El verificador de ficheros de clases

Se encarga de validar los bytecodes. Algunas de las comprobaciones que lleva a cabo son:
  • Que las variables estén inicializadas antes de ser utilizadas
  • Que las llamadas a un método coinciden con los tipos de referencias a objetos
  • Que no se han infrigido las reglas para el acceso a los métodos y clases privados, etc.

El gestor de seguridad

Es una clase que controla si está permitida una determinada operación.

Alguna de las operaciones que comprueban son las siguientes:
  • Si el hilo actual puede cargar un subproceso
  • Si puede acceder a un paquete específico
  • Si puede acceder o modificar las propiedades del sistema
  • Si puede leer desde o escribir en un fichero específico
  • SI puede eliminar un fichero específico
  • Si puede aceptar una conexión socket desde un host o número de puerto específico, etc.

Por defecto no se instala de forma automática ningún gestor de seguridad cuando se ejecuta una aplicación Java. En el siguiente ejemplo veremos la salida que produce el programa ejecutándolo sin gestor de seguridad y con gestor de seguridad. El programa muestra los valores de ciertas propiedades de sistema (usamos el método System.getProperty(propiedad) para mostrar los valores), la siguiente tabla describe alguna de las más importantes:

Archivo EJEMPLO1.JAVA

public class Ejemplo1 {
     
      public static void main(String[] args) {
           
            //propiedades de sistema en un array
            String t[] = {    "java.class.path", "java.home", "java.vendor",
                                   "java.version", "os.name", "os.version","user.dir",
                                   "user.home", "user.name"};
           
           
            System.setProperty ("java.security.policy", System.getProperty("user.dir") + "\\src\\_01SinConGestor\\Politica1.policy");
            System.setSecurityManager(new SecurityManager());
            for (int i = 0; i < t.length; i++) {
                 
                  System.out.print("Propiedad:" + t[i]);
                  try {
                        String s = System.getProperty(t[i]);     //valor de la propiedad
                        System.out.println("\t==> " + s);
                  } catch (Exception e) { System.err.println("\n\tEXcepción " + e.toString()); }
           
            }//Fin de for
      }//Fin de main
}//Fin de Ejemplo1

Archivo POLITICA1.POLICY (para política de permisos)

grant {
      permission java.util.PropertyPermission "java.class.path", "read";
      permission java.util.PropertyPermission "java.home", "read";
      permission java.util.PropertyPermission "user.home", "read";
      permission java.util.PropertyPermission "user.name", "read";
      permission java.util.PropertyPermission "user.dir", "read";
      permission java.util.PropertyPermission "on.version", "read";
};




APIs Java para seguridad

JCA (Java Cryptography Architecture, Arquitectura Criptográfica de Java). Es un marco de trabaj para acceder y desarrollar funciones criptográficas en la plataforma Java. Proporciona la infraestructura para la ejecución de los principales servicios de cifrado, incluyendo:
  • Firmas digitales
  • Resúmenes de mensajes (hash)
  • Certificados y validación de certificados
  • Encriptación (cifrado de bloques, cifrado simétrico y asimétrico)
  • Generación y gestión de claves y generación segura de números aleatorios

JSSE (Java Secure Extension, Extensión de Sockets Seguros Java). Es un conjunto de paquetes Java provistos para la comunicación segura en Internet. Implementa una versión Java de los protocolos SSL y TLS. Incluye funcionalidades como:
  • Cifrado de datos
  • Autenticación del servidor
  • Integridad de mensajes
  • Autenticación del cliente

JAAS (Java Authentication and Authorization Service, Servicio de Autenticación y Autorización de Java). Es un interfaz que permite a las aplicaciones Java acceder a servicios de control de autenticación y acceso. Puede usarse con dos fines:
  • La autenticación de usuarios para conocer quién está ejecutando código Java.
  • La autorización de usuarios para garantizar que quién lo ejecuta tiene los permisos necesarios para hacerlo.

Ficheros de política en Java

Localización

El fichero de políticas predeterminado java.policy, está localizado en los siguientes directorios.
  • Sistemas operativos Windows: java.home\lib\security\java.policy
  • Sistemas UNIX: java.home/lib/security/java.policy

Donde java.home representa el valor de la propiedad java.home, que especifica el directorio en el que se instaló el JRE.

El valor por defecto es tener un solo fichero de políticas en todo el sistema y un fchero de políticas en el directorio home (dado por la variable user.home) del usuario (este tiene un punto delante).

En el fichero java.security localizado en la carpeta java.home\lib\security\ se encuentran las localizaciones de estos ficheros:

policy.url.1=file:${java.home}/lib/security/java.policy
policy.url.2=file:${user.home}/.java.policy

Entrada grant

Un fichero de políticas especifica qué permisos están disponibles para el código de varias fuentes.

Se utiliza para conceder permisos del sistema.

Contiene una secuencia de entradas grant, cada una de ellas tiene una o más entradas, el formato básico es el siguiente:

grant codeBase "URL"{
      permission Nombre_clase "Nombre_destino", "Acción";
      permission Nombre_clase "Nombre_destino", "Acción";
}

A la derecha de codeBase se indica la ubicación del código base sobre el que se van a definir los permisos. Su valor es una URL y siempre se debe utiliza la barra diagonal (/) como separador de directorio incluso para las URL de tipo file en Windows. Por ejemplo: grant codeBase “file:/C:/somepath/api/”.
Si se omite codeBase entonces los permisos se aplican a todas las fuentes.

Nombre_clase contiene el nombre de la clase de permisos, por ejemplo:
  • java.io.FilePermission (representa acceso a fichero o directorio)
  • java.net.SocketPermission (acceso a la red vía socket)
  • java.util.PropertyPermission (permiso sobre propiedades del sistema), etc.
El parámetro Nombre_destino especifica el destino del permiso y depende de la clase de permiso.
Por ejemplo si la clase de permiso es java.io.FilePermission en Nombre_destino se puede poner un fichero o un directorio; si la clase es java.net.SocketPermission se pondría un servidor y un número de puerto; si es java.util.PropertyPermission se pondría una propiedad del sistema.

En el parámetro Acción se indica una lista de acciones separadas por comas, por ejemplo read, write, delete o execute para una clase de permiso java.io.FilePermission; accept, listen, connect, resolve para una clase de permiso java.netSocketPermission; read, write para una clase de permiso java.util.PropertyPermission.

Desde la URL:

http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html

Se puede consultar los permisos, sus destinos y acciones.

Ejemplo de ficheros de políticas

Creamos un fichero en una carpeta determinada (en Windows C:\Ficheros) e insertamos una línea, después lee el contenido del fichero y lo muestra en pantalla. El comportamiento del programa será diferente si usamos o no el gestor de seguridad y ficheros de políticas.

Se añade el gestor de seguridad en el método main(), la línea System.setSecurityManager(new SecurityManager()); antes de la cláusula try, la salida muestra un error de acceso denegado al escribir datos en el fichero, tampoco se podrá realizar la lectura del mismo.

Archivo EJEMPLO2.JAVA

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Ejemplo2 {
     
      public static void main(String[] args) {
           
            //El directorio C:/Ficheros debe estar creado previamente.
            //System.setProperty ("java.security.policy", System.getProperty("user.dir") + "\\src\\_02FicherosPoliticas\\Politica2.policy");
            //System.setSecurityManager(new SecurityManager());
            try {
                  //escritura en fichero
                  BufferedWriter fichero = new BufferedWriter (new FileWriter("C://Ficheros//Fichero.txt"));
                  fichero.write("Escritura de una linea en fichero.");
                  fichero.newLine(); // escribe un salto de línea
                  fichero.close();
                  System.out.println("Final proceso de escritura...");
                 
                  //lectura del fichero
                  BufferedReader fichero2 = new BufferedReader (new FileReader("C://Ficheros//Fichero.txt"));
                  String linea = fichero2.readLine();
                  System.out.println("Contenido del fichero: ");
                  System.out.println("\t" + linea);
                  fichero2.close();
                  System.out.println("Final proceso de lectura...");
            } catch (FileNotFoundException fn) {
                  System.out.println("No se encuentra el fichero");
            } catch (IOException io) {
                  System.out.println("Error de E/S ");
            } catch (Exception e) {
                  System.out.println("ERROR => " + e.toString());
            }//Fin de try
           
      }// Fin de main   
}// Fin de Ejemplo2

Ahora se crea el siguiente fichero de políticas de nombre Politica2.policy con el siguiente contenido:

Archivo POLITICA2.POLICY

grant codeBase  "file:${user.dir}/bin/"{
      permission java.io.FilePermission "C:\\Ficheros\\*", "read, write";
};

que permite a los programas localizados en la carpeta indicada (incluido permiso para crear) ficheros en la ruta C\Ficheros.

Formato grant

El parámetro Nombre_destino para la clase de permiso java.io.FilePermission puede terminar en una serie de caracteres:
  • “/” (donde “/” es el carácter separador de ficheros) indica un directorio y todos los ficheros contenidos en ese directorio.
  • “/-“ indica un directorio y (recursivamente) todos los ficheros y subdirectorios contenidos en ese directorio.
  • “<<ALL FILES>>” indica cualquier fichero
  • También es posible usar propiedades de sistema como user.dir o user.home para dar privilegios sobre el directorio en el que se está ejecutando el programa o sobre el directorio por defecto del usuario. Se debe utilizar la propiedad encerrada entre llaves y con el caracer $ delante. ${propiedad}.
El parámetro Nombre_destino para la clase de permiso java.net.SocketPermission tiene el siguiente formato:

Host = (nombre del host | Direccion IP) [:número de puerto] donde número de puerto = N | -N | N-[M]

Las acciones son accept, listen, connect y resolve.
El host se especifica como un nombre de host o dirección IP.
Especificación de puerto:
  • “N”, puerto N
  • “N-“, todos los puertos desde N en adelante
  • “-N”, todos los puertos desde N hacia atrás
  • “M-N”, rango de puertos

Tercer ejemplo de ficheros de políticas de Sockets

Archivo EJEMPLO3SERVIDOR.JAVA

import java.net.ServerSocket;
import java.net.Socket;

public class Ejemplo3Servidor {
     
      public static void main(String[] arg) {
           
            int numeroPuerto = 6000;// Puerto
            ServerSocket servidor = null;
            System.setProperty ("java.security.policy", System.getProperty("user.dir") + "\\src\\_03FicherosPoliticasSockets\\Politica3.policy");
            System.setSecurityManager(new SecurityManager());
           
            try {
                  servidor = new ServerSocket(numeroPuerto);
                  System.out.println("Esperando al cliente.....");
                  Socket clienteConectado = servidor.accept();
                  System.out.println("Cliente conectado.");
                  clienteConectado.close();
                  System.out.println("Cliente desconectado.");
                  servidor.close();
            } catch (Exception e) { System.err.println("ERROR=> " + e.toString()); }
     
      }// Fin de main
}//Fin de Ejemplo3Servidor

Archivo EJEMPLO3.JAVA

import java.net.Socket;

public class Ejemplo3 {
     
      public static void main(String[] args) {
           
            String Host = "localhost";
            int Puerto = 6000;
            System.setProperty ("java.security.policy", System.getProperty("user.dir") + "\\src\\_03FicherosPoliticasSockets\\Politica4.policy");
            System.setSecurityManager(new SecurityManager());   
           
            try {
                  Socket Cliente = new Socket(Host, Puerto);
                  System.out.println("CLIENTE INICIADO.");
                  Cliente.close();
                  System.out.println("CLIENTE FINALIZADO.");
            } catch (Exception e) { System.err.println("ERROR=> " + e.toString()); }
           
      }// Fin de main  
}//Fin de Ejemplo3

Archivo POLITICA3.POLICY

/*Politica para el SERVIDOR*/
grant codeBase "file:${user.dir}/bin/" {
  permission java.net.SocketPermission "localhost", "listen, accept";
};

Archivo POLITICA4.POLICY

/*Politica para el CLIENTE*/
grant codeBase "file:${user.dir}/bin/" {
  permission java.net.SocketPermission "localhost:6000", "connect";
};

El programa servidor escucha y acepta peticiones de clientes en el puerto 6000, cuando el cliente se conecta se visualiza un mensaje y al cerrarse también.

El programa cliente se conecta al servidor en el puerto 6000, visualiza unos mensajes y luego se desconecta.

El fichero de políticas para el servidor, Politica3.policy, representa un permiso que permite a los programas localizados en la carpeta ${user.dir}/bin/ escuchar y aceptar conexiones en localhost.

El fichero de políticas para el cliente, Politica4.policy, representa un permiso que permite a los programas localizados en la carpeta ${user.dir}/bin/ conectarse al puerto 6000 en localhost.

Herramienta PolicyTool

Se recomienda usar la herramienta para editar cualquier fichero de políticas y verificar la sintaxis de su contenido.
Para lanzarla, desde la terminal escribiremos el comando policytool.


0 comments:

Publicar un comentario