Salta el contingut
 

JSSE – Java Secure Socket Extension

Autor: Joan Puigcerver Ibáñez

Correu electrònic: j.puigcerveribanez@edu.gva.es

Llicència: CC BY-NC-SA 4.0

(Reconeixement - NoComercial - CompartirIgual) 🅭

JSSE – Java Secure Socket Extension

JSSE significa Java Secure Socket Extension, que és un conjunt de classes de Java que proporcionen comunicació de xarxa segura. Permet una comunicació segura a Internet mitjançant els protocols SSL (Secure Sockets Layer) i TLS (Transport Layer Security).

JSSE proporciona objectes SSLSocket i SSLServerSocket per realitzar la comunicació segura.

Diferències entre Socket i SSLSocket

La classe SSLSocket és una subclasse de la classe Socket, i per tant, la utilització de qualsevol dels dos objectes és el mateix.

La diferència principal entre utilitzar SSLSocket i Socket és que SSLSocket proporciona comunicació segura a la xarxa, mentre que Socket proporciona comunicació no segura.

Quan s'utilitza SSLSocket, totes les dades enviades entre el client i el servidor es xifren i es desencripten automàticament, protegint les dades de ser interceptades o modificades durant la transmissió.

El xifratge de dades amb SSLSocket es basa en els protocols SSL (Secure Sockets Layer) i TLS (Transport Layer Security). Quan s'utilitza SSLSocket, el protocol SSL/TLS estableix una connexió segura entre el client i el servidor realitzant una sèrie de passos, com ara negociar un algorisme de xifratge comú, intercanviar claus de xifratge i verificar la identitat del servidor.

Una vegada establerta una connexió segura, totes les dades transmeses entre el client i el servidor es xifren mitjançant un algorisme de xifratge simètric, com AES o DES. La clau de xifratge utilitzada per a aquest procés es genera de forma dinàmica i és única per a cada sessió.

Quan les dades xifrades arriben a l'altre extrem de la connexió, es desxifra amb la mateixa clau i algorisme de xifratge. Això proporciona seguretat d'extrem a extrem per a les dades que es transmeten, assegurant que no es puguen interceptar ni modificar durant la transmissió.

Per evitar que la clau simètrica puga ser obtinguda en el procés de «handshake», les dades son encriptades mitjançant la clau pública i privada del client i el servidor.

A més del xifratge de dades, SSLSocket també proporciona integritat de les dades calculant un codi d'autenticació de missatges (MAC) per a cada missatge. El MAC s'utilitza per verificar que les dades no s'han modificat durant la transmissió, assegurant l'autenticitat de les dades. Aquesta MAC és calculada mitjançant una funció hash, com SHA-1 o SHA-256.

KeyStore i TrustStore

Per poder utilitzar els SSLSocket cal utilitzar certificats per validar la identitat del servidor.

  • En el servidor, utilitzarem una KeyStore, per guardar la clau pública i privada del servidor.
  • En el client, utilitzarem una TrustStore, on es guardaran els certificats dels servidors de confiança.

Els passos que s'han seguit:

  • Creem una KeyStore i generem un certificat per al servidor:

    keytool -genkey -keyalg RSA \
        -alias server_exampleJSSE \
        -keystore files/ud4/jsse/server_exampleJSSE_keystore.jks \
        -storepass 123456 -keysize 2048 \
        -dname "CN=Example JSSE, OU=PSP-DAM2S, O=CIPFP Mislata, L=Mislata, ST=València, C=ES"
    

  • Exportem el certificat del servidor generat a un fitxer:

    keytool -export -alias server_exampleJSSE \
        -keystore files/ud4/jsse/server_exampleJSSE_keystore.jks \
        -file files/ud4/jsse/server_exampleJSSE.crt \
        -storepass 123456
    

  • Importem el certificat del servidor a una TrustStore, que utilitzarà el client:

    keytool -import -alias server_exampleJSSE \
        -keystore files/ud4/jsse/client_exampleJSSE_truststore.jks \
        -file files/ud4/jsse/server_exampleJSSE.crt \
        -storepass 123456
    

Exemple en Java

Com es pot observar en el següent codi font, la utilització de SSLSocket i SSLServerSocket és la mateixa que Socket i ServerSocket.

L'única diferència és la creació dels objectes:

  • Es realitza mitjançant SSLSocketFactory i SSLServerSocketFactory.
  • Cal especificar la KeyStore en el servidor i la TrustStore en el client.

Fitxer amb la configuració

En aquest exemple s'ha utilitzat un fitxer de configuració .properties per especificar les dades sensibles o que poden canviar ens els diferents entorns (desenvolupament, test, producció).

El valor d'aquestes variables pot ser modificat en cada entorn diferent. En el nostre entorn, hem creat el fitxer config.properties, on s'ha guardat la informació:

ud4.examples.jsse.host=localhost
ud4.examples.jsse.port=1234
ud4.examples.jsse.keystore.passwd=123456

Servidor

SimpleJsseServer.java
package ud4.examples.jsse;

import ud4.examples.Config;

import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import java.io.*;
import java.net.ConnectException;
import java.net.ServerSocket;
import java.util.Properties;

public class SimpleJsseServer {

    public static void main(String[] args) {
        try {
            Properties config = Config.getConfig("application.properties");
            int port = Integer.parseInt(config.getProperty("ud4.examples.jsse.port"));
            System.out.println("Creant el Socket servidor en el port: " + port);

            System.setProperty("javax.net.ssl.keyStore", "files/ud4/server_exampleJSSE_keystore.jks");
            System.setProperty("javax.net.ssl.keyStorePassword", config.getProperty("ud4.examples.jsse.keystore.passwd"));

            SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
            ServerSocket server = sslserversocketfactory.createServerSocket(port);
            // ServerSocket server = new ServerSocket(port);

            System.out.println("Esperant connexions...");
            // Aquest Socket es de tipus SSLSocket
            SSLSocket connexio = (SSLSocket) server.accept();
            System.out.println("Connectat amb el client!");

            BufferedReader in = new BufferedReader(new InputStreamReader(connexio.getInputStream()));
            // Activem l'opció autoFlush
            PrintWriter out = new PrintWriter(connexio.getOutputStream(), true);

            System.out.println("Esperant missatge des del client...");
            String missatge = in.readLine();
            System.out.println("Sha rebut el missatge:");
            System.out.println(missatge);

            String resposta = "Rebut!";
            System.out.println("S'ha enviat el missatge: " + resposta);
            out.println(resposta);

            System.out.println("Tancant el servidor...");
            connexio.close();
            server.close();
            System.out.println("Tancat.");
        } catch (ConnectException e) {
            System.err.println("Connection refused!");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Client

SimpleJsseClient.java
package ud4.examples.jsse;

import ud4.examples.Config;

import javax.net.ssl.SSLSocketFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.Socket;
import java.util.Properties;

public class SimpleJsseClient {
    public static void main(String[] args) {
        try {
            Properties config = Config.getConfig("application.properties");
            String host = config.getProperty("ud4.examples.jsse.host");
            int port = Integer.parseInt(config.getProperty("ud4.examples.jsse.port"));
            String keyStorePassword = config.getProperty("ud4.examples.jsse.keystore.passwd");

            System.out.println("Creant el Socket client.");
            System.setProperty("javax.net.ssl.trustStore", "files/ud4/client_exampleJSSE_truststore.jks");
            System.setProperty("javax.net.ssl.trustStorePassword", keyStorePassword);

            SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
            Socket socket = sslsocketfactory.createSocket(host, port);
            // Socket socket = new Socket(host, port);

            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // Es pot utilitzar l'opció autoflush per forçar l'enviament de dades
            // després de cada print()
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

            String missatge = "Aquest missatge ha segut enviat des del client.";
            out.println(missatge);
            out.flush(); // Aquesta línia no és necessària amb l'opció autoFlush
            System.out.println("S'ha enviat el missatge.");

            System.out.println("Esperant resposta");
            String resposta = in.readLine();

            System.out.println("Resposta del servidor:");
            System.out.println(resposta);

            System.out.println("Tancant el socket...");
            socket.close();
            System.out.println("Tancat");
        } catch (ConnectException e) {
            System.err.println("Connection refused!");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Comentaris