Activitats

Polítiques de seguretat

L’objectiu d’aquesta activitat és ser capaç d’executar correctament un programa donat sota el gestor de seguretat per defecte de Java.

Amb el següent programa, feu el que sigui necessari per poder-lo executar correctament, sense que doni cap error de seguretat, sota el gestor per defecte de Java. Per fer-ho, no es pot modificar el seu codi font.

Abans de fer res, però, analitzeu-ne el codi per saber què fa i assolir executar-lo correctament sense necessitat de cap gestor de seguretat.

import java.io.File;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class Activitat1 {

  public static void main (String[] args) throws Exception {
    String home = System.getProperty("user.dir");
    if (!"".equals(home)) {
      File f = new File (home + File.separator + "names.txt");
      if (f.isFile()) {
        Scanner rd = new Scanner(f);        
        while (rd.hasNext()) {
          String name = rd.nextLine();
          String result = "";
          try {          
            InetAddress ip = InetAddress.getByName(name);
            result = ip + "...";
            Socket s = new Socket(name,80);
            System.out.println(result + "AVAILABLE");
          } catch (UnknownHostException ex) {
            System.out.println(name + " does not exist!");
          } catch (ConnectException ex) {
            System.out.println(result + "UNAVAILABLE");
          }
        }
        rd.close();
      }      
    }
    
  }
  
}

Per poder dur a terme aquesta tasca, cal crear un fitxer de polítiques que permeti dur a terme les accions restringides dins el codi. Tot seguit es mostra un possible fitxer correcte, d’entre diverses possibilitats.

grant {
  permission java.util.PropertyPermission "user.dir", "read";
  permission java.io.FilePermission "${user.dir}${/}names.txt", "read";
  permission java.net.SocketPermission "*:80", "connect,resolve";
};

Signatura de codi

L’objectiu d’aquesta activitat és signar digitalment el codi d’una classe i usar aquesta firma per permetre-li permisos exclusius.

Amb el programa de l’apartat anterior, signeu-ne el codi amb un certificat propietat de IOC Developer, que es troba estudiant el cicle de DAM a l’IOC i viu a Girona.

Un cop fet això, adopteu el rol de IOC User, un altre estudiant del mateix cicle, però que viu a Tarragona. Feu el que faci falta per garantir que en el vostre ordinador només el codi dels programes signats per IOC Developer puguin dur a terme les accions restringides que té el codi de l’exercici anterior.

Sota el rol de IOC Developer primer de tot cal seguir les passes per generar un fitxer JAR signat a partir de la classe resultant.

Primer cal generar un parell de claus a un magatzem.

keytool -genkey -alias iocdev -keyalg RSA -keystore iocdev.jks -keysize 2048 -validity 365

Enter keystore password:
Re-enter new password:
What is your first and last name?
  [Unknown]:  IOC Developer
What is the name of your organizational unit?
  [Unknown]:  DAM
What is the name of your organization?
  [Unknown]:  IOC
What is the name of your City or Locality?
  [Unknown]:  Girona
What is the name of your State or Province?
  [Unknown]:  Catalunya
What is the two-letter country code for this unit?
  [Unknown]:  ES
Is CN=IOC Developer, OU=DAM, O=IOC, L=Girona, ST=Catalunya, C=ES correct?
  [no]:  yes

Enter key password for <dev1>
        (RETURN if same as keystore password):
Re-enter new password:

Després, es genera el fitxer JAR a partir de la classe.

jar cvf act9.jar Activitat9.class

Un cop fet això, signem el fitxer JAR usant la clau de desenvolupador.

jarsigner -keystore iocdev.jks -signedjar act9-signed.jar act9.jar iocdev

Finalment, caldrà exportar el certificat autosignat per passar-li a l’usuari de manera segura.

keytool -export -keystore iocdev.jks -alias iocdev -file iocdev.cer

Ara ja adoptem el rol de l’usuari, que ha de disposar també d’un magatzem de claus, creat de la mateixa manera que el desenvolupador. Suposem que el fitxer es diu iocuser.jks.

Primer, caldrà importar el certificat del desenvolupador com un certificat de confiança.

keytool -import -alias iocdev -keystore iocuser.jks -file iocdev.cer

Un cop fet, ja només cal indicar al fitxer de polítiques de seguretat que el bloc de permisos només aplica per codi signat pel propietari del certificat d’ IOC Developer, que es troba desat al magatzem a l’entrada iocdev. Això es fa afegint l’opció SignedBy. A part d’això, no cal tocar ni una sola coma més.

grant SignedBy "iocdev" {
  permission java.util.PropertyPermission "user.dir", "read";
  permission java.io.FilePermission "${user.dir}${/}names.txt", "read";
  permission java.net.SocketPermission "*:80", "connect,resolve";
};

Anàlisi del protocol SSL

L’objectiu d’aquesta activitat és comprovar que el protocol SSL protegeix les dades enviades a través de la xarxa.

Descarregueu-vos i instal·leu l’analitzador de xarxes de programari lliure anomenat Wireshark. Aquest analitzador permet inspeccionar les dades que viatgen per la vostra xarxa local.

Pàgina de descàrrega: http://www.wireshark.org/

Compareu el resultat de connectar-vos a Google mitjançant el seu servidor web no segur (http:///www.google.com) i el seu servidor segur (https:///www.google.com). En el darrer cas, identifiqueu les diferents passes de la negociació SSL.

Adaptació d'un servei existent a SSL

L’objectiu d’aquesta activitat és que partint d’un servei que no treballa amb SSL, veure què cal fer per transformar el seu codi Java de manera que es basi exclusivament en aquest protocol.

Com a punt de partida es disposa del següent codi Java, que implementa un servei en xarxa:

import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Scanner;

public class TimeServer {

  private static final String[] nomDies = {"Diumenge", "Dilluns", "Dimarts", 
                                           "Dimecres","Dijous", "Divendres", 
                                           "Dissabte"};
  
  public static void main(String[] argv) throws Exception {

    try {
      ServerSocket srvSocket = new ServerSocket(10000);
      
      while (true) {
        Socket cliSocket = srvSocket.accept();

        Scanner in = new Scanner (cliSocket.getInputStream());
        int[] data = new int[3];
        boolean ok = true;
        for (int i = 0; i < data.length; i++) {
          if (in.hasNextInt()) {
            data[i] = in.nextInt();
          } else {
            ok = false;  
          }          
        }
                
        PrintStream out = new PrintStream(cliSocket.getOutputStream());        
        if (ok) {
          data[1] -= 1;
          GregorianCalendar cal = new GregorianCalendar(data[2], data[1], data[0]);
          int dia = cal.get(Calendar.DAY_OF_WEEK) - 1;
          out.println("Aquest dia era " + nomDies[dia] + ".");
          
        } else {
          out.println("Format de les dades incorrecte.");
        }
        cliSocket.close();
      }
    } catch (Exception ex) {
      System.out.println("Error en les comuncacions: " + ex);
    }
  }
  
}

Resoleu ordenadament els següents apartats.

1. Esbrineu què fa el servei i genereu un client no segur que s’hi comuniqui correctament. La seva classe es pot anomenar TimeClient.

Aquest servei diu el dia de la setmana d’una data concreta, codificada com a text dia mes any (separats per espais). Una possible versió d’un client compatible amb aquest servei pot ser:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

public class TimeClient {

  public static void main(String[] argv) throws Exception {

    String data = "24 6 2013";

    try {
      Socket cliSocket =  new Socket("localhost", 10000);

      PrintStream out = new PrintStream(cliSocket.getOutputStream());
      BufferedReader in = new BufferedReader(new InputStreamReader(cliSocket.getInputStream()));
      
      out.println(data);
      String res = in.readLine();      
      System.out.println(res);
            
      cliSocket.close();      
    } catch (Exception ex) {
      System.out.println("Error en les comuncacions: " + ex);
    }
  }  
}

2. Genereu dues noves classes Java, anomenades SecureTimeServer i Secure TimeClient que adaptin el client i el servei al protocol SSL amb autenticació simple. Tots els fitxers associats a claus i certificats es desen a la carpeta SSL, tant al client com al servei.

Per poder crear una versió segura del servei, caldrà configurar el magatzem de clau del servei, exportar el seu certificat i importar-lo al magatzem de confiança del client.

keytool -genkey -alias "server" -keyalg RSA -keystore ServerKeyStore.jks -keysize 2048

keytool -export -keystore ServerKeyStore.jks -alias "server" -file server.crt

keytool -importcert -file server.crt -keystore ClientKeyStore.jks -alias "server"

Un cop fet, a nivell de codi Java, cada versió segura es basa en reemplaçar l’ús de les classes ServerSocket i Soocket per SSLServerSocket i SSLSocket, així com configurar els diferents magatzems.

import java.io.PrintStream;
import java.net.Socket;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Scanner;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.Socket;

public class SecureTimeServer {

  private static final String[] nomDies = {"Diumenge", "Dilluns", "Dimarts", 
                                           "Dimecres","Dijous", "Divendres", 
                                           "Dissabte"};
  
  public static void main(String[] argv) throws Exception {
    
    System.setProperty("javax.net.ssl.keyStore", "SSL/ServerkeyStore.jks");
    System.setProperty("javax.net.ssl.keyStorePassword", "server");
    
    
    try {
      SSLServerSocketFactory sslFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
      SSLServerSocket srvSocket = (SSLServerSocket) sslFactory.createServerSocket(10000);
      
      srvSocket.setNeedClientAuth(true);
      
      while (true) {
        Socket cliSocket = srvSocket.accept();
        
        ...
  
}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import javax.net.ssl.Socket;
import javax.net.ssl.SSLSocketFactory;

public class SecureTimeClient {
  public static void main(String[] argv) throws Exception {

    String data = "24 6 2013";

    System.setProperty("javax.net.ssl.trustStore", "SSL/ClientKeyStore.jks");
    System.setProperty("javax.net.ssl.trustStorePassword", "client");
    
    try {
      SSLSocketFactory sslFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
      Socket cliSocket = sslFactory.createSocket("localhost", 10000);
    
      ...
  }   
}

Obtenció del certificat del servidor

L’objectiu d’aquesta activitat és processar les dades associades al certificat d’un servidor segur mitjançant el protocol SSL.

Un exemple molt habitual de comprovació sobre les dades del certificat es dóna quan una aplicació client es connecta a un servei. Normalment, cal veure si el nom que conté al camp Common Name del propietari és correspon amb el nom DNS de la màquina. Això es fa per garantir que la clau i el certificat s’està usant realment per la màquina per la qual es van emetre, i no per una altra.

Genereu el codi d’un client que es connecti a un servei mitjançant SSL i validi si el nom de la màquina es correspon al que hi ha al camp de Common Name del seu certificat.

Si suposem que el servei s’executa al port 10000 de la màquina local, i el magatzem de certificats de confiança es troba a la ruta SSL/ClientKeyStore.jks amb contrasenya client:

public class NameValidatorClient {
  public static void main(String[] argv) throws Exception {

    System.setProperty("javax.net.ssl.trustStore", "SSL/ClientKeyStore.jks");
    System.setProperty("javax.net.ssl.trustStorePassword", "client");
    
    try {
      SSLSocketFactory sslFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
      Socket cliSocket = sslFactory.createSocket("localhost", 10000);
           
      PrintStream out = new PrintStream(cliSocket.getOutputStream());
      BufferedReader in = new BufferedReader(new InputStreamReader(cliSocket.getInputStream()));           
                 
      if (checkHostName((SSLSocket)cliSocket)) {
        System.out.println("El nom és correcte");    
      } else {
        System.out.println("El nom és incorrecte");
      }
                  
      cliSocket.close();      
    } catch (Exception ex) {
      System.out.println("Error en les comunicacions: " + ex);
    }
  }  
  
  private static boolean checkHostName (SSLSocket socket) throws Exception {
    SSLSession ses = socket.getSession();
    String host = ses.getPeerHost();
    Principal p = ses.getPeerPrincipal();
    String name = p.getName();
    String[] dn = p.getName().split(",");
    String cn = null;
    for (int i=0; i < dn.length;i++) {
      if (dn[i].startsWith("CN=")) {
        cn = dn[i].substring(3);
      }   
    }
    return cn.equals(host);
  }  
    
}

Autenticació mútua

L’objectiu d’aquesta activitat és crear un servidor i un client segurs mitjançant SSL, de manera que el client també estigui obligat a identificar-se mitjançant un certificat digital (autenticació mútua).

Modifiqueu el codi de les classes SecureTimeClient i SecureTimeClient de manera que ara funcionin mitjançant autenticació mútua. Useu el mateix fitxer tant com a magatzem de claus i com a magatzem pels certificats de confiança, de manera que no faci falta gestionar dos fitxers diferents per cada aplicació.

Per establir l’autenticació mútua, cal que el client ara disposi de la seva clau privada i exportar el seu certificat al client:

keytool -genkey -alias "client" -keyalg RSA -keystore ClientKeyStore.jks -keysize 2048

keytool -export -keystore ClientKeyStore.jks -alias "client" -file client.crt

keytool -importcert -file client.crt -keystore ServerKeyStore.jks -alias "client"

Un cop fet, cal configurar les propietats del sistema adients a les dues parts i indicar al servei que el client s’ha d’autenticar forçosament:

public class SecureTimeServer {

  private static final String[] nomDies = {"Diumenge", "Dilluns", "Dimarts", 
                                           "Dimecres","Dijous", "Divendres", 
                                           "Dissabte"};
  
  public static void main(String[] argv) throws Exception {

    
    System.setProperty("javax.net.ssl.trustStore", "SSL/ServerKeyStore.jks");
    System.setProperty("javax.net.ssl.trustStorePassword", "server");
    
    System.setProperty("javax.net.ssl.keyStore", "SSL/ServerkeyStore.jks");
    System.setProperty("javax.net.ssl.keyStorePassword", "server");
    
    
    try {
      SSLServerSocketFactory sslFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
      SSLServerSocket srvSocket = (SSLServerSocket) sslFactory.createServerSocket(10000);
      
      srvSocket.setNeedClientAuth(true);

      ...

}
public class SecureTimeClient {
  public static void main(String[] argv) throws Exception {

    String data = "24 6 2013";

    System.setProperty("javax.net.ssl.trustStore", "SSL/ClientKeyStore.jks");
    System.setProperty("javax.net.ssl.trustStorePassword", "client");

    System.setProperty("javax.net.ssl.keyStore", "SSL/ClientkeyStore.jks");
    System.setProperty("javax.net.ssl.keyStorePassword", "client");

    
    try {
      SSLSocketFactory sslFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
      Socket cliSocket = sslFactory.createSocket("localhost", 10000);
      
      ...
}

Control d'usuaris

L’objectiu d’aquesta activitat és establir a través d’un gestor de confiança personalitzat si un client pot accedir o no a un servei segons la identitat continguda al seu certificat digital.

Genereu un servei que disposi d’una llista negra d’usuaris, de manera que encara que disposin d’un certificat generat per un emissor de confiança, se’ls denegui el pas, considerant-los invàlids. Sempre que això passi, el servei ha de mostrar un missatge pel canal d’error estàndard indicant-ho. Per dur a terme això, useu un gestor de confiança personalitzat anomenat BlackListTrustManager.

La llista negra pot ser un fitxer de text amb una llista de noms associats al camp Common Name dels certificats dels clients a qui es vol barrar el pas. Cada nom va a una línia diferent. Ara bé, aquesta llista s’ha de poder actualitzar en tot moment sense haver d’aturar el servei abans.

El codi del nou gestor és:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.security.KeyStore;
import java.security.Principal;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Scanner;
import javax.net.ssl.X509TrustManager;
import sun.security.validator.Validator;

public class BlackListTrustManager  implements X509TrustManager {

  private ArrayList<X509Certificate> certs = new ArrayList<X509Certificate>();
  private File blackList;
  
  public BlackListTrustManager(String pathList, String pathStore, String pwd) throws Exception {
    KeyStore keyStore = KeyStore.getInstance("JKS");
    keyStore.load(new FileInputStream(pathStore), pwd.toCharArray());
    Enumeration<String> aliases = keyStore.aliases();
    while(aliases.hasMoreElements()) {
      String a = aliases.nextElement();
      if (keyStore.isCertificateEntry(a)) {
        certs.add((X509Certificate)keyStore.getCertificate(a)); 
      }        
    }
    blackList = new File(pathList);
    if (!blackList.isFile()) {
      throw new Exception ("No existeix el fitxer amb la llista negra.");
    }        
 }

  @Override
     public void checkClientTrusted(X509Certificate[] chain, String authType)
                 throws CertificateException {
      try {
         Validator val = Validator.getInstance(Validator.TYPE_PKIX, Validator.VAR_TLS_SERVER, certs);
         X509Certificate[] result = val.validate(chain);
         String cn = getCN(chain[0]);
         Scanner reader = new Scanner(blackList); 
         while (reader.hasNext()) {
           String name = reader.nextLine();
           if (name.equalsIgnoreCase(cn)) {
             String msg = "Aquest usuari està a la llista negra: " + cn;  
             System.err.println(msg);  
             throw new CertificateException(msg);
           }
         }    
       } catch (FileNotFoundException ex) {
         throw new CertificateException("No s'ha pogut contrastar la llista negra.");  
       }
     }

  @Override
     public void checkServerTrusted(X509Certificate[] chain, String authType)
                 throws CertificateException {
      throw new CertificateException("Aquest gestor només és per aplicacions servidor.");
     }

  @Override
     public X509Certificate[] getAcceptedIssuers() {
       X509Certificate[] issuers = new X509Certificate[certs.size()];
       for (int i = 0; i< issuers.length; i++){
         issuers[i] = certs.get(i);    
       }
       return issuers;
     }

    private String getCN(X509Certificate cert) {
      Principal p = cert.getSubjectDN();
      String[] dn = p.getName().split(",");
      String cn = "";
      for (int i=0; i < dn.length;i++) {
        if (dn[i].startsWith("CN=")) {
          cn = dn[i].substring(3);
        }   
      }      
      return cn;
    }

}

Per inicialitzar-lo, caldrà fer al servei el següent, comptant que la ruta a la llista negra és SSL/blacklist.txt, el magatzem de certificats de confiança és a SSL/ServerkeyStore.jks, el magatzem de claus és a SSL/ServerKeyStore.jks i la contrasenya és server per tot:

      ...
      KeyManagerFactory kmf = kmf = KeyManagerFactory.getInstance("PKIX");
      FileInputStream fin = new FileInputStream("SSL/ServerKeyStore.jks");
      KeyStore ks = KeyStore.getInstance("JKS");
      ks.load(fin, "server".toCharArray());
      kmf.init(ks, "server".toCharArray());
            
      TrustManager[] bl = new TrustManager[] { new BlackListTrustManager("SSL/blacklist.txt", "SSL/ServerkeyStore.jks", "server" )};
      
      SSLContext sslContext = SSLContext.getInstance( "SSL" );
                  
      sslContext.init( kmf.getKeyManagers(), bl, new java.security.SecureRandom() );    
      SSLServerSocketFactory sslFactory = sslContext.getServerSocketFactory();                     
      SSLServerSocket srvSocket = (SSLServerSocket) sslFactory.createServerSocket(10000);
        
      srvSocket.setNeedClientAuth(true);
      ...

Gestió de certificats de confiança personalitzada

L’objectiu d’aquesta activitat és gestionar un seguit de certificats de confiança mitjançant un sistema diferent del magatzem de claus, amb l’ajut d’un gestor personalitzat.

Crear una aplicació client que gestioni els certificats de confiança directament mitjançant els fitxers amb extensió CRT exportats amb Keytool. Tots els certificats de confiança es desen a una carpeta. Per afegir o treure un certificat de confiança que el gestor reconegui, l’usuari només ha de copiar o esborrar el fitxer corresponent abans de posar en marxa l’aplicació.

Per fer aquesta tasca, cal generar un gestor de confiança personalitzat anomenat FileCertTrustManager.

El codi del gestor personalitzat podria ser:

import java.io.File;
import java.io.FileInputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import javax.net.ssl.X509TrustManager;
import sun.security.validator.Validator;

public class FileCertTrustManager implements X509TrustManager {  
    
  private ArrayList<X509Certificate> certs = new ArrayList<X509Certificate>();

  
  public FileCertTrustManager(String ruta) throws Exception {
    File dir = new File(ruta);
    if (dir.isDirectory()) {
      File[] content = dir.listFiles();
      for (File f: content) {
        if (f.isFile() && f.getName().endsWith(".crt")) {
           CertificateFactory cf = CertificateFactory.getInstance("X.509");
           X509Certificate cert = (X509Certificate)cf.generateCertificate(new FileInputStream(f));
           certs.add(cert);
        }    
      }
    } else {
      throw new Exception("La ruta de la carpeta no existeix.");    
    }
  }

  @Override
     public void checkClientTrusted(X509Certificate[] chain, String authType)
                 throws CertificateException {
       throw new CertificateException("Aquest gestor només és per aplicacions client.");      
     }

  @Override
     public void checkServerTrusted(X509Certificate[] chain, String authType)
                 throws CertificateException {
         try {
           Validator val = Validator.getInstance(Validator.TYPE_PKIX, Validator.VAR_TLS_CLIENT, certs);
           X509Certificate[] result = val.validate(chain);
         } catch (Exception e) {
           throw new CertificateException("El certificat del servei no és de confiança.");
         }       
     }

  @Override
     public X509Certificate[] getAcceptedIssuers() {
       X509Certificate[] issuers = new X509Certificate[certs.size()];
       for (int i = 0; i< issuers.length; i++){
         issuers[i] = certs.get(i);    
       }
       return issuers;
     }

    
}

Per incialitzar-lo, caldria fer al client:

      ...
      KeyManagerFactory kmf = kmf = KeyManagerFactory.getInstance("PKIX");
      FileInputStream fin = new FileInputStream("SSL/ClientKeyStore.jks");
      KeyStore ks = KeyStore.getInstance("JKS");
      ks.load(fin, "client".toCharArray());
      kmf.init(ks, "client".toCharArray());
            
      TrustManager[] trustAllCerts = new TrustManager[] { new FileCertTrustManager("SSL")};
      
      SSLContext sslContext = SSLContext.getInstance( "SSL" );
                  
      sslContext.init( kmf.getKeyManagers(), trustAllCerts, new java.security.SecureRandom() );    
      SSLSocketFactory sslFactory = sslContext.getSocketFactory();     
        
      Socket cliSocket = sslFactory.createSocket("localhost", 10000);
      ...

Anar a la pàgina anterior:
Aplicacions Java segures a Internet
Anar a la pàgina següent:
Exercicis d'autoavaluació