Crea applicazioni di rete sicure con SSL e l'API JSSE

Internet è un posto pericoloso. È semplicemente troppo facile ficcare il naso, falsificare e rubare informazioni non protette mentre viaggiano sui cavi. Il mese scorso, ho scritto l'ultimo articolo di una serie sui certificati X.509 e l'infrastruttura a chiave pubblica (PKI), le tecnologie che proteggono la maggior parte delle attività di e-commerce su Internet. Verso la fine dell'articolo, ho suggerito di esaminare il protocollo SSL (Secure Socket Layer) per scoprire come vengono utilizzati nella pratica i certificati X.509. SSL è l'app killer X.509: quasi tutti i browser e i server Web e applicativi più popolari lo supportano.

Questo mese esplorerò SSL come implementato da JSSE (Java Secure Socket Extension) e vi mostrerò come creare applicazioni di rete sicure in Java utilizzando SSL e JSSE.

Cominciamo con una semplice dimostrazione. JSSE fornisce un toolkit SSL per le applicazioni Java. Oltre alle classi e alle interfacce necessarie, JSSE fornisce un pratico interruttore di debug della riga di comando che è possibile utilizzare per osservare il protocollo SSL in azione. Oltre a fornire informazioni utili per il debug di un'applicazione recalcitrante, giocare con il toolkit è un ottimo modo per bagnarsi i piedi con SSL e JSSE.

Per eseguire la dimostrazione, devi prima compilare la seguente classe:

public class Test {public static void main (String [] arstring) {try {new java.net.URL ("//" + arstring [0] + "/"). getContent (); } catch (eccezione eccezione) {exception.printStackTrace (); }}}

Successivamente, è necessario attivare il debug SSL ed eseguire l'applicazione sopra. L'applicazione si connette al sito Web protetto specificato sulla riga di comando utilizzando il protocollo SSL tramite HTTPS. La prima opzione carica il gestore del protocollo HTTPS. La seconda opzione, l'opzione di debug, fa sì che il programma stampi il suo comportamento. Ecco il comando (sostituire con il nome di un server Web sicuro):

 java -Djava.protocol.handler.pkgs = com.sun.net.ssl.internal.www.protocol -Djavax.net.debug = ssl Test  

Devi installare JSSE; fai riferimento a Risorse se non sei sicuro di come.

Ora mettiamoci al lavoro e parliamo di SSL e JSSE.

Un breve sguardo a SSL

Il codice nell'introduzione mostra il modo più semplice per aggiungere SSL alle tue applicazioni, tramite la java.net.URLclasse. Questo approccio è utile, ma non è abbastanza flessibile da consentire di creare un'applicazione sicura che utilizza socket generici.

Prima di mostrarti come aggiungere quella flessibilità, diamo una rapida occhiata alle funzionalità di SSL.

Come suggerisce il nome, SSL mira a fornire alle applicazioni un toolkit simile a un socket. Idealmente, dovrebbe essere facile convertire un'applicazione che utilizza socket regolari in un'applicazione che utilizza SSL.

SSL risolve tre importanti problemi di sicurezza:

  1. Fornisce l'autenticazione, che aiuta a garantire la legittimità delle entità coinvolte in un dialogo.
  2. Fornisce privacy. SSL aiuta a garantire che una terza parte non possa decifrare il dialogo tra due entità.
  3. Mantiene l'integrità. L'uso di un MAC (codice di autenticazione del messaggio), simile a un checksum, aiuta a garantire che un dialogo tra due entità non venga modificato da terzi.

SSL fa molto affidamento sulla crittografia sia a chiave pubblica che a chiave segreta. Utilizza la crittografia a chiave segreta per crittografare in blocco i dati scambiati tra due applicazioni. SSL fornisce la soluzione ideale perché gli algoritmi a chiave segreta sono sia sicuri che veloci. La crittografia a chiave pubblica, che è più lenta della crittografia a chiave segreta, è una scelta migliore per l'autenticazione e lo scambio di chiavi.

L'implementazione di riferimento JSSE di Sun viene fornita con tutta la tecnologia necessaria per aggiungere SSL alle applicazioni. Include il supporto per la crittografia RSA (Rivest-Shamir-Adleman), lo standard de facto per la sicurezza su Internet. Include un'implementazione di SSL 3.0 - l'attuale standard SSL - e TLS (Transport Layer Security) 1.0, la prossima generazione di SSL. JSSE fornisce anche una suite di API per la creazione e l'utilizzo di socket sicuri.

L'API JSSE

L'architettura di sicurezza Java utilizza pesantemente il modello di progettazione Factory . Per chi non lo sapesse, il modello di progettazione Factory utilizza oggetti factory speciali per costruire istanze, invece di chiamare direttamente i loro costruttori. (Vedi Risorse per i pro e i contro della classe Factory.)

In JSSE, tutto inizia con la fabbrica; c'è una fabbrica per i socket SSL e una fabbrica per i socket del server SSL. Poiché i socket generici e quelli del server sono già fondamentali per la programmazione di rete Java, presumo che tu abbia familiarità con i due e che tu ne comprenda i ruoli e le differenze. Se non lo sei, ti consiglio di prendere un buon libro sulla programmazione di rete Java.

SSLSocketFactory

I metodi della javax.net.ssl.SSLSocketFactoryclasse si dividono in tre categorie. Il primo consiste in un metodo statico singolo che recupera la fabbrica presa SSL predefinita: static SocketFactory getDefault().

La seconda categoria è costituita da quattro metodi ereditati dal javax.net.SocketFactorymirror dei quattro costruttori chiave trovati nella java.net.Socketclasse e da un metodo che avvolge un socket esistente con un socket SSL. Ciascuno restituisce un socket SSL:

  1. Socket createSocket(String host, int port)
  2. Socket createSocket(String host, int port, InetAddress clientHost, int clientPort)
  3. Socket createSocket(InetAddress host, int port)
  4. Socket createSocket(InetAddress host, int port, InetAddress clientHost, int clientPort)
  5. Socket createSocket(Socket socket, String host, int port, boolean autoClose)

I due metodi nella terza categoria restituiscono l'elenco delle suite di crittografia SSL abilitate per impostazione predefinita e l'elenco completo delle suite di crittografia SSL supportate:

  1. String [] getDefaultCipherSuites()
  2. String [] getSupportedCipherSuites()

Una suite di crittografia è una combinazione di algoritmi crittografici che definiscono un particolare livello di sicurezza per una connessione SSL. Una suite di crittografia definisce se la connessione è crittografata, se viene verificata l'integrità del contenuto e come avviene l'autenticazione.

SSLServerSocketFactory

I metodi della javax.net.ssl.SSLServerSocketFactoryclasse rientrano nelle stesse tre categorie di SSLSocketFactory. In primo luogo, v'è il metodo statico singolo che recupera il server SSL presa di fabbrica: static ServerSocketFactory getDefault().

I metodi che restituiscono i socket del server SSL rispecchiano i costruttori trovati nella java.net.ServerSocketclasse:

  1. ServerSocket createServerSocket(int port)
  2. ServerSocket createServerSocket(int port, int backlog)
  3. ServerSocket createServerSocket(int port, int backlog, InetAddress address)

Infine, SSLServerSocketFactorypresenta i due metodi che restituiscono rispettivamente l'elenco delle crittografie abilitate per impostazione predefinita e l'elenco delle crittografie supportate:

  1. String [] getDefaultCipherSuites()
  2. String [] getSupportedCipherSuites()

Finora, l'API è piuttosto semplice.

SSLSocket

Le cose si fanno interessanti in javax.net.ssl.SSLSocketclasse. Presumo che tu abbia già familiarità con i metodi forniti dal suo genitore, la Socketclasse, quindi mi concentrerò sui metodi che forniscono funzionalità relative a SSL.

Come le due classi factory SSL, i primi due metodi elencati di seguito recuperano rispettivamente le suite di crittografia SSL abilitate e supportate. Il terzo metodo imposta le suite di crittografia abilitate. Un'applicazione può utilizzare la terza operazione per eseguire l'upgrade o il downgrade dell'intervallo di sicurezza accettabile consentito dall'applicazione:

  1. String [] getEnabledCipherSuites()
  2. String [] getSupportedCipherSuites()
  3. void setEnabledCipherSuites(String [] suites)

These two methods determine whether the socket can establish new SSL sessions, which maintain connection details -- like the shared secret key -- between connections:

  1. boolean getEnableSessionCreation()
  2. void setEnableSessionCreation(boolean flag)

The next two methods determine whether the socket will require client authentication. The methods only make sense when invoked on server mode sockets. Remember, according to the SSL specification, client authentication is optional. For example, most Web applications don't require it:

  1. boolean getNeedClientAuth()
  2. void setNeedClientAuth(boolean need)

The methods below change the socket from client mode to server mode. This affects who initiates the SSL handshake and who authenticates first:

  1. boolean getUseClientMode()
  2. void setUseClientMode(boolean mode)

Method void startHandshake() forces an SSL handshake. It's possible, but not common, to force a new handshake operation in an existing connection.

Method SSLSession getSession() retrieves the SSL session. You will seldom need to access the SSL session directly.

The two methods listed below add and remove an SSL handshake listener object. The handshake listener object is notified whenever an SSL handshake operation completes on the socket.

  1. void addHandshakeCompletedListener(HandshakeCompletedListener listener)
  2. void removeHandshakeCompletedListener(HandshakeCompletedListener listener)

SSLServerSocket

The javax.net.ssl.SSLServerSocket class is similar to the javax.net.ssl.SSLSocket class; it doesn't require much individual attention. In fact, the set of methods on javax.net.ssl.SSLServerSocket class is a subset of the methods on the javax.net.ssl.SSLSocket class.

The first two methods listed below retrieve the enabled and supported SSL cipher suites. The third method sets the enabled cipher suite:

  1. String [] getEnabledCipherSuites()
  2. String [] getSupportedCipherSuites()
  3. void setEnabledCipherSuites(String [] suites)

These two methods control whether or not the server socket can establish new SSL sessions:

  1. boolean getEnableSessionCreation()
  2. void setEnableSessionCreation(boolean flag)

The following methods determine whether the accepted sockets will require client authentication:

  1. boolean getNeedClientAuth()
  2. void setNeedClientAuth(boolean flag)

The methods below change the accepted socket from client mode to server mode:

  1. boolean getUseClientMode()
  2. void setUseClientMode(boolean flag)

A simple example

To make this toolkit tutorial clearer, I've included the source code for a simple server and a compatible client below. It's a secure variation on the typical echo application that many introductory networking texts provide.

The server, shown below, uses JSSE to create a secure server socket. It listens on the server socket for connections from secure clients. When running the server, you must specify the keystore to use. The keystore contains the server's certificate. I have created a simple keystore that contains a single certificate. (See Resources to download the certificate.)

import java.io.InputStream; import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.IOException; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; public class EchoServer { public static void main(String [] arstring) { try { SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory)SSLServerSocketFactory.getDefault(); SSLServerSocket sslserversocket = (SSLServerSocket)sslserversocketfactory.createServerSocket(9999); SSLSocket sslsocket = (SSLSocket)sslserversocket.accept(); InputStream inputstream = sslsocket.getInputStream(); InputStreamReader inputstreamreader = new InputStreamReader(inputstream); BufferedReader bufferedreader = new BufferedReader(inputstreamreader); String string = null; while ((string = bufferedreader.readLine()) != null) { System.out.println(string); System.out.flush(); } } catch (Exception exception) { exception.printStackTrace(); } } } 

Use the following command to start the server (foobar is both the name of the keystore file and its password):

 java -Djavax.net.ssl.keyStore=foobar -Djavax.net.ssl.keyStorePassword=foobar EchoServer 

The client, shown below, uses JSSE to securely connect to the server. When running the client, you must specify the truststore to use, which contains the list of trusted certificates. I have created a simple truststore that contains a single certificate. (See Resources to download the certificate.)

import java.io.InputStream; import java.io.OutputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; public class EchoClient { public static void main(String [] arstring) { try { SSLSocketFactory sslsocketfactory = (SSLSocketFactory)SSLSocketFactory.getDefault(); SSLSocket sslsocket = (SSLSocket)sslsocketfactory.createSocket("localhost", 9999); InputStream inputstream = System.in; InputStreamReader inputstreamreader = new InputStreamReader(inputstream); BufferedReader bufferedreader = new BufferedReader(inputstreamreader); OutputStream outputstream = sslsocket.getOutputStream(); OutputStreamWriter outputstreamwriter = new OutputStreamWriter(outputstream); BufferedWriter bufferedwriter = new BufferedWriter(outputstreamwriter); String string = null; while ((string = bufferedreader.readLine()) != null) { bufferedwriter.write(string + '\n'); bufferedwriter.flush(); } } catch (Exception exception) { exception.printStackTrace(); } } } 

Utilizzare il seguente comando per avviare il client ( foobarè sia il nome del file truststore che la relativa password):

 java -Djavax.net.ssl.trustStore = foobar -Djavax.net.ssl.trustStorePassword = foobar EchoClient