<div class="page"> <div class="cover text-center"> <img class="mx-auto" src=/itb/images/logo_mislata.png alt="logo"> # Java Secure Socket Extension (JSSE) <div class="text-end fit-content ms-auto my-3 mt-auto pt-3"> <p><strong>Autor:</strong> Joan Puigcerver Ibáñez</p> <p><strong>Correu electrònic:</strong> j.puigcerveribanez@edu.gva.es</p> <p><strong>Curs:</strong> 2024/2025</p> </div> <div> <p class="fw-bold mb-0">Llicència: BY-NC-SA</p> <p class="d-none d-md-block">(Reconeixement - No Comercial - Compartir Igual)</p> <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.ca" target="_blank"> <img class="mx-auto" src="/itb/images/license.png" alt="Licence"/> </a> </div><!--license--> </div><!--cover--> </div><!--page--> {:toc} ## Introducció __JSSE__ significa __Java Secure Socket Extension__, que és un conjunt d'API 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`](https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLSocket.html) és una subcalsse de la classe `Socket`, i per tant, la utilització de qualsevol dels dos objectes és el màteix. 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 - https://stackoverflow.com/questions/47434877/how-to-generate-keystore-and-truststore - https://unix.stackexchange.com/questions/347116/how-to-create-keystore-and-truststore-using-self-signed-certificate - https://stackoverflow.com/questions/22737970/secret-key-ssl-socket-connections-in-java Per poder utilitzar els `SSLSocket` cal utilitzar certificats per validar l'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: ```bash 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: ```bash 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: ```bash 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ó: ```cfg ud4.examples.jsse.host=localhost ud4.examples.jsse.port=1234 ud4.examples.jsse.keystore.passwd=123456 ``` ### Servidor ```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 ```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); } } } ```