Erstellen Sie sichere Netzwerkanwendungen mit SSL und der JSSE-API

Das Internet ist ein gefährlicher Ort. Es ist einfach zu einfach, ungeschützte Informationen zu schnüffeln, zu fälschen und zu stehlen, wenn sie über die Kabel übertragen werden. Letzten Monat schrieb ich den letzten Artikel in einer Reihe über X.509-Zertifikate und Public-Key-Infrastruktur (PKI), die Technologien, die die meisten E-Commerce-Aktivitäten im Internet sichern. Gegen Ende des Artikels schlug ich vor, das SSL-Protokoll (Secure Socket Layer) zu lesen, um zu erfahren, wie X.509-Zertifikate in der Praxis verwendet werden. SSL ist die Killer-App X.509 - fast jeder Browser und die beliebtesten Web- und Anwendungsserver unterstützen sie.

In diesem Monat werde ich SSL untersuchen, wie es von JSSE (Java Secure Socket Extension) implementiert wurde, und Ihnen zeigen, wie Sie sichere Netzwerkanwendungen in Java mit SSL und JSSE erstellen.

Beginnen wir mit einer einfachen Demonstration. JSSE bietet ein SSL-Toolkit für Java-Anwendungen. Zusätzlich zu den erforderlichen Klassen und Schnittstellen bietet JSSE einen praktischen Befehlszeilen-Debugging-Schalter, mit dem Sie das SSL-Protokoll in Aktion überwachen können . Das Spielen mit dem Toolkit bietet nicht nur nützliche Informationen zum Debuggen einer widerspenstigen Anwendung, sondern ist auch eine hervorragende Möglichkeit, Ihre Füße mit SSL und JSSE nass zu machen.

Um die Demonstration auszuführen, müssen Sie zuerst die folgende Klasse kompilieren:

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

Als Nächstes müssen Sie das SSL-Debugging aktivieren und die oben genannte Anwendung ausführen. Die Anwendung stellt über das SSL-Protokoll über HTTPS eine Verbindung zu der sicheren Website her, die Sie in der Befehlszeile angeben. Die erste Option lädt den HTTPS-Protokollhandler. Die zweite Option, die Debug-Option, veranlasst das Programm, sein Verhalten auszudrucken. Hier ist der Befehl (durch den Namen eines sicheren Webservers ersetzen ):

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

Sie müssen JSSE installieren. Weitere Informationen finden Sie unter Ressourcen.

Kommen wir nun zur Sache und sprechen über SSL und JSSE.

Ein kurzer Blick auf SSL

Der Code in der Einführung zeigt, wie Sie Ihren Anwendungen am einfachsten SSL hinzufügen können - über die java.net.URLKlasse. Dieser Ansatz ist nützlich, aber nicht flexibel genug, um eine sichere Anwendung zu erstellen, die generische Sockets verwendet.

Bevor ich Ihnen zeige, wie Sie diese Flexibilität hinzufügen können, werfen wir einen kurzen Blick auf die Funktionen von SSL.

Wie der Name schon sagt, zielt SSL darauf ab, Anwendungen ein sicheres Socket-ähnliches Toolkit bereitzustellen. Im Idealfall sollte es einfach sein, eine Anwendung, die normale Sockets verwendet, in eine Anwendung zu konvertieren, die SSL verwendet.

SSL behebt drei wichtige Sicherheitsprobleme:

  1. Es bietet eine Authentifizierung, mit deren Hilfe die Legitimität der an einem Dialog beteiligten Entitäten sichergestellt werden kann.
  2. Es bietet Privatsphäre. Mit SSL kann sichergestellt werden, dass ein Dritter den Dialog zwischen zwei Entitäten nicht entschlüsseln kann.
  3. Es behält die Integrität bei. Durch die Verwendung eines MAC (Nachrichtenauthentifizierungscodes), der einer Prüfsumme ähnelt, wird sichergestellt, dass ein Dialog zwischen zwei Entitäten nicht von Dritten geändert wird.

SSL basiert stark auf Kryptografie mit öffentlichem und geheimem Schlüssel. Es verwendet die Kryptografie mit geheimen Schlüsseln, um die zwischen zwei Anwendungen ausgetauschten Daten in großen Mengen zu verschlüsseln. SSL bietet die ideale Lösung, da Secret-Key-Algorithmen sowohl sicher als auch schnell sind. Die Kryptografie mit öffentlichem Schlüssel, die langsamer als die Kryptografie mit geheimem Schlüssel ist, ist eine bessere Wahl für die Authentifizierung und den Schlüsselaustausch.

Die JSSE-Referenzimplementierung von Sun verfügt über die gesamte Technologie, die zum Hinzufügen von SSL zu Ihren Anwendungen erforderlich ist. Es enthält RSA-Kryptografieunterstützung (Rivest-Shamir-Adleman) - den De-facto-Standard für die Sicherheit im Internet. Es enthält eine Implementierung von SSL 3.0 - dem aktuellen SSL-Standard - und TLS (Transport Layer Security) 1.0, der nächsten Generation von SSL. JSSE bietet auch eine Reihe von APIs zum Erstellen und Verwenden sicherer Sockets.

Die JSSE-API

Die Java-Sicherheitsarchitektur verwendet stark das Factory- Entwurfsmuster. Für Uneingeweihte verwendet das Factory-Entwurfsmuster spezielle Factory- Objekte, um Instanzen zu erstellen, anstatt ihre Konstruktoren direkt aufzurufen. (Siehe Ressourcen für die Vor- und Nachteile der Factory-Klasse.)

In JSSE beginnt alles mit der Fabrik. Es gibt eine Fabrik für SSL-Sockets und eine Fabrik für SSL-Server-Sockets. Da generische Sockets und Server-Sockets für die Java-Netzwerkprogrammierung bereits von grundlegender Bedeutung sind, gehe ich davon aus, dass Sie mit den beiden vertraut sind und deren Rollen und Unterschiede verstehen. Wenn nicht, empfehle ich Ihnen, ein gutes Buch über Java-Netzwerkprogrammierung zu lesen.

SSLSocketFactory

Methoden in der javax.net.ssl.SSLSocketFactoryKlasse fallen in drei Kategorien. Die erste besteht aus einer einzelnen statischen Methode, mit der die Standard-SSL-Socket-Factory abgerufen wird : static SocketFactory getDefault().

Die zweite Kategorie besteht aus vier Methoden, die von javax.net.SocketFactorydiesem Spiegel der vier Schlüsselkonstruktoren der java.net.SocketKlasse geerbt wurden , und einer Methode, die einen vorhandenen Socket mit einem SSL-Socket umschließt. Sie geben jeweils einen SSL-Socket zurück:

  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)

Die beiden Methoden in der dritten Kategorie geben die Liste der standardmäßig aktivierten SSL-Verschlüsselungssuiten und die vollständige Liste der unterstützten SSL-Verschlüsselungssuiten zurück:

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

Eine Cipher Suite ist eine Kombination aus kryptografischen Algorithmen, die eine bestimmte Sicherheitsstufe für eine SSL-Verbindung definieren. Eine Verschlüsselungssuite definiert, ob die Verbindung verschlüsselt ist, ob die Inhaltsintegrität überprüft wird und wie die Authentifizierung erfolgt.

SSLServerSocketFactory

Methoden für die javax.net.ssl.SSLServerSocketFactoryKlasse fallen in die gleichen drei Kategorien wie SSLSocketFactory. Erstens gibt es die einzige statische Methode, mit der die Standard-SSL-Server-Socket-Factory abgerufen wird : static ServerSocketFactory getDefault().

Die Methoden, die SSL-Server-Sockets zurückgeben, spiegeln die in der java.net.ServerSocketKlasse gefundenen Konstruktoren wider:

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

Finally, the SSLServerSocketFactory features the two methods that return the list of ciphers enabled by default and the list of supported ciphers, respectively:

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

So far, the API is pretty straightforward.

SSLSocket

Things get interesting in the javax.net.ssl.SSLSocket class. I assume you are already familiar with the methods provided by its parent, the Socket class, so I will concentrate on the methods that provide SSL-related functionality.

Like the two SSL factory classes, the first two methods listed below retrieve the enabled and supported SSL cipher suites, respectively. The third method sets the enabled cipher suites. An application can use the third operation to upgrade or downgrade the range of acceptable security that the application will allow:

  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(); } } } 

Verwenden Sie den folgenden Befehl, um den Client zu starten ( foobarist sowohl der Name der Truststore-Datei als auch das Kennwort):

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