Joven hacker sonriendo

Hackeamos su software

cero falsos positivos

Inteligencia experta + Tecnología especializada
DXST - SAST - IAST - SCA - DevSecOps
Caja blanca - Caja gris - Caja negra
Atacando Aplicaciones Web, APIs, Apps Móviles,
Cliente Servidor, Servidores, Redes, Dispositivos IoT
IoT SCI: Sistemas de Control Industrial

Autenticar Servidor para Clientes Sin Certificado

Nuestros ethical hackers explican cómo evitar vulnerabilidades de seguridad mediante la configuración segura en Java al autenticar el servidor para clientes sin un certificado digital. Ésto resulta útil cuando no es práctico o implementable la generación de certificados en el lado del cliente.

Necesidad

Autenticar servidor de red para clientes que no poseen un certificado digital.

Contexto

A continuación se describen las circunstancias bajo las cuales la siguiente solución tiene sentido:

  1. Se está desarrollando una aplicación en Java.

  2. No es práctico o implementable la generación de certificados digitales del lado del cliente.

Solución

Los siguientes pasos describen la manera en que un cliente se autentica ante un servidor:

  1. Las conexiones SSL soportan autenticación de una y dos vías.

  2. En la autenticación de una vía, el servidor presenta un certificado digital al cliente para probar su identidad. El cliente realiza dos pasos para validar el certificado:

    • Verifica que el certificado digital fue firmado por una autoridad confiable.

    • Valida que el nombre de host en el certificado coincide con el nombre del servidor

  3. Si las validaciones son exitosas, se establece la conexión SSL.

  4. Para poder soportar de manera adecuada los requerimientos de la PKI en las conexiones SSL, es necesario contar con un sistema de almacenamiento de material criptográfico.

  5. La clase Keystore permite administrar esta información. Como tal, soporta:

    • Claves privadas.

    • Clave secreta (para criptografía simétrica).

    • Certificados confiables (para validar la identidad de otras partes).

  6. En el siguiente código se crea un contenedor de llaves.

    keystore.java
    1
    2
    3
    4
    String KEYSTORE = "cacerts";
    // ¿dónde tenemos las claves?
    KeyStore keystore = KeyStore.getInstance("JKS");
    keystore.load(new FileInputStream(KEYSTORE), null);
    
  7. Primero se obtiene una instancia de contenedor de tipo Java keystore, y se carga el archivo para administrar la información.

  8. La fábrica abstracta de administradores de claves requiere un tipo de algoritmo a usar. En este caso usamos certificados X.509 que son los apropiados para SSL. Además,inicializamos el administrador con el contenedor de llaves.

    1
    2
    3
    // ¿quién nos las va a administrar?
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    kmf.init(keystore, null);
    
  9. La fábrica abstracta de administradores de confianza crea objetos que permiten validar la identidad utilizando certificados confiables. Su inicialización es similar a la de los administradores de claves.

    1
    2
    3
    // ¿quién nos va a verificar las claves?
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
    tmf.init(keystore);
    
  10. El paso siguiente es crear una fábrica de fábricas de sockets seguros, para esto es necesario relacionar los administradores creados previamente.

    1
    2
    // ¿quién nos va a cifrar la conexión?
    ServerSocketFactory ssf = sslc.getServerSocketFactory();
    
  11. Con la fábrica de sockets de servidores se puede crear el servidor que va a atender las conexiones seguras. Note que para configurar el comportamiento de una vía se establece que el cliente no requiere autenticación.

    1
    2
    3
    4
    SSLServerSocket server = (SSLServerSocket) ssf.createServerSocket(port);
    server.setNeedClientAuth(false);
    // recibir conexión
    SSLSocket client = (SSLSocket) server.accept();
    
  12. La clase SSLServerSocket en realidad deriva de Socket, por lo que la comunicación puede hacerse como un socket convencional.

    1
    2
    3
    4
    5
    6
    7
    8
    // escribir
    BufferedOutputStream outputStream = new BufferedOutputStream(client.getOutputStream());
    outputStream.write("Este es el mensaje enviado".getBytes());
    outputStream.flush();
    System.out.println("Servidor: mensaje enviado.");
    // cerrar
    client.close();
    server.close();
    
  13. Es importante aclarar que el servidor y el cliente difieren en la forma que usan el socket. Es decir, los clientes invocan objetos de tipo SSLSocket quién nos va a cifrar la conexión

    1
    2
    SocketFactory sf = sslc.getSocketFactory();
    SSLSocket client = (SSLSocket) sf.createSocket(host, port);
    
  14. Código del servidor:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    import java.io.BufferedOutputStream;
    import java.io.FileInputStream;
    import java.security.KeyStore;
    import javax.net.ServerSocketFactory;
    import javax.net.ssl.KeyManagerFactory;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLServerSocket;
    import javax.net.ssl.SSLSocket;
    import javax.net.ssl.TrustManagerFactory;
    
    public class Server {
      public static void main(String[] args) throws Exception {
        int port = 3333;
        String KEYSTORE = "certs";
        char[] KEYSTOREPW = "storepass".toCharArray();
        char[] KEYPW = "keypass".toCharArray();
    
        // ¿dónde tenemos las claves?
    
        KeyStore keystore = KeyStore.getInstance("JKS");
        keystore.load(new FileInputStream(KEYSTORE), KEYSTOREPW);
    
        // ¿quién nos las va a administrar?
    
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        vkmf.init(keystore, KEYPW);
    
        // ¿quién nos va a verificar las claves?
        // (no se requiere si no se realiza autenticacion de cliente)
    
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(keystore);
    
        // ¿cómo nos van a cifrar la conexión?
    
        SSLContext sslc = SSLContext.getInstance("SSLv3");
        sslc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
    
      // ¿quién nos va a cifrar la conexión?
    
        ServerSocketFactory ssf = sslc.getServerSocketFactory();
        SSLServerSocket server = (SSLServerSocket) ssf.createServerSocket(port);
        server.setNeedClientAuth(false);
    
      // recibir conexión
    
        SSLSocket client = (SSLSocket) server.accept();
    
      // leer
    
        BufferedOutputStream outputStream = new BufferedOutputStream(client.getOutputStream());
        outputStream.write("Este es el mensaje enviado".getBytes());
        outputStream.flush();
        System.out.println("Servidor: mensaje enviado.");
    
      // cerrar
    
      client.close();
        server.close();
      }
    }
    
  15. Código del cliente:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    import java.io.BufferedInputStream;
    import java.io.FileInputStream;
    import java.security.KeyStore;
    import javax.net.SocketFactory;
    import javax.net.ssl.KeyManagerFactory;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSocket;
    import javax.net.ssl.TrustManagerFactory;
    
    public class Client
    {
     public static void main(String[] args) throws Exception
     {
       String host = "localhost";
       int port = 3333;
       String KEYSTORE = "cacerts";
    
       // ¿dónde tenemos las claves?
    
       KeyStore keystore = KeyStore.getInstance("JKS");
       keystore.load(new FileInputStream(KEYSTORE), null);
    
       // ¿quién nos las va a administrar?
    
       KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
       kmf.init(keystore, null);
    
       // ¿quién nos va a verificar las claves?
    
       TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
       tmf.init(keystore);
    
       // ¿cómo nos van a cifrar la conexion?
    
       SSLContext sslc = SSLContext.getInstance("SSLv3");
       sslc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
    
       // ¿quién nos va a cifrar la conexion?
    
       SocketFactory sf = sslc.getSocketFactory();
       SSLSocket client = (SSLSocket) sf.createSocket(host, port);
    
       // leer
    
       BufferedInputStream inputStream = new BufferedInputStream(client.getInputStream());
       byte[] message = new byte[64];
       inputStream.read(message);
       System.out.println("Cliente: mensaje: " + new String(message));
    
       // cerrar
    
       client.close();
      }
    }
    

Descargas

Puedes descargar el código fuente pulsando en los siguientes enlaces:

client.java Código de autenticación del lado cliente.

server.java Código de autenticación del lado servidor.




Haz un comentario

Estado de los servicios - Términos de Uso