SSLとJSSEAPIを使用して安全なネットワークアプリケーションを構築する

インターネットは危険な場所です。保護されていない情報がネットワーク上を移動するときに、その情報を盗聴、なりすまし、盗むのは簡単すぎます。先月、私はX.509証明書と公開鍵インフラストラクチャ(PKI)に関するシリーズの最後の記事を書きました。これは、インターネット上のほとんどのeコマースアクティビティを保護するテクノロジです。記事の終わり近くで、SSL(Secure Socket Layer)プロトコルを調べて、X.509証明書が実際にどのように使用されるかを学ぶことを提案しました。SSLはX.509キラーアプリです。ほぼすべてのブラウザーと最も人気のあるWebサーバーおよびアプリケーションサーバーがSSLをサポートしています。

今月は、JSSE(Java Secure Socket Extension)によって実装されたSSLについて説明し、SSLとJSSEを使用してJavaで安全なネットワークアプリケーションを構築する方法を紹介します。

簡単なデモンストレーションから始めましょう。JSSEは、Javaアプリケーション用のSSLツールキットを提供します。必要なクラスとインターフェースに加えて、JSSEは、SSLプロトコルの動作を監視するために使用できる便利なコマンドラインデバッグスイッチを提供します。扱いにくいアプリケーションをデバッグするための有用な情報を提供することに加えて、ツールキットで遊ぶことは、SSLとJSSEで足を濡らすための優れた方法です。

デモンストレーションを実行するには、最初に次のクラスをコンパイルする必要があります。

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

次に、SSLデバッグをオンにして、上記のアプリケーションを実行する必要があります。アプリケーションは、HTTPS経由でSSLプロトコルを使用して、コマンドラインで指定した安全なWebサイトに接続します。最初のオプションは、HTTPSプロトコルハンドラーをロードします。2番目のオプションであるdebugオプションを使用すると、プログラムはその動作を出力します。コマンドは次のとおりです(安全なWebサーバーの名前に置き換えてください)。

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

JSSEをインストールする必要があります。方法がわからない場合は、「参考文献」を参照してください。

それでは、ビジネスに取り掛かり、SSLとJSSEについて話しましょう。

SSLの概要

イントロダクションのコードは、java.net.URLクラスを介してアプリケーションにSSLを追加する最も簡単な方法を示しています。このアプローチは便利ですが、汎用ソケットを使用する安全なアプリケーションを作成できるほど柔軟ではありません。

その柔軟性を追加する方法を説明する前に、SSLの機能を簡単に見てみましょう。

その名前が示すように、SSLはアプリケーションに安全なソケットのようなツールキットを提供することを目的としています。理想的には、通常のソケットを使用するアプリケーションをSSLを使用するアプリケーションに簡単に変換できる必要があります。

SSLは、次の3つの重要なセキュリティ問題に対処します。

  1. 認証を提供し、ダイアログに関係するエンティティの正当性を保証するのに役立ちます。
  2. それはプライバシーを提供します。SSLは、サードパーティが2つのエンティティ間のダイアログを解読できないことを保証するのに役立ちます。
  3. それは完全性を維持します。チェックサムに似たMAC(メッセージ認証コード)を使用すると、2つのエンティティ間のダイアログがサードパーティによって変更されないことが保証されます。

SSLは、公開鍵暗号と秘密鍵暗号の両方に大きく依存しています。秘密鍵暗号化を使用して、2つのアプリケーション間で交換されるデータを一括暗号化します。秘密鍵アルゴリズムは安全で高速であるため、SSLは理想的なソリューションを提供します。秘密鍵暗号化よりも低速な公開鍵暗号化は、認証と鍵交換に適しています。

SunのJSSEリファレンス実装には、SSLをアプリケーションに追加するために必要なすべてのテクノロジーが付属しています。これには、RSA(Rivest-Shamir-Adleman)暗号化サポート(インターネット上のセキュリティの事実上の標準)が含まれています。これには、現在のSSL標準であるSSL 3.0と、次世代のSSLであるTLS(Transport Layer Security)1.0の実装が含まれています。JSSEは、セキュアソケットを作成および使用するための一連のAPIも提供します。

JSSE API

Javaセキュリティアーキテクチャは、ファクトリデザインパターンを多用しています。初心者の場合、ファクトリデザインパターンは、コンストラクタを直接呼び出すのではなく、特別なファクトリオブジェクトを使用してインスタンスを構築します。(ファクトリクラスの長所と短所については、リソースを参照してください。)

JSSEでは、すべてが工場から始まります。SSLソケット用のファクトリとSSLサーバーソケット用のファクトリがあります。汎用ソケットとサーバーソケットはすでにJavaネットワークプログラミングの非常に基本的なものであるため、この2つに精通しており、それらの役割と違いを理解していることを前提としています。そうでない場合は、Javaネットワークプログラミングに関する優れた本を読むことをお勧めします。

SSLSocketFactory

javax.net.ssl.SSLSocketFactoryクラスのメソッドは3つのカテゴリに分類されます。1つ目は、デフォルトのSSLソケットファクトリを取得する単一の静的メソッドで構成されていますstatic SocketFactory getDefault()

2番目のカテゴリはjavax.net.SocketFactoryjava.net.Socketクラスで見つかった4つのキーコンストラクタをミラーリングする4つのメソッドと、既存のソケットをSSLソケットでラップする1つのメソッドで構成されます。それぞれが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)

3番目のカテゴリの2つのメソッドは、デフォルトで有効になっているSSL暗号スイートのリストと、サポートされているSSL暗号スイートの完全なリストを返します。

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

暗号スイートは、SSL接続の特定のレベルのセキュリティを定義する暗号化アルゴリズムの組み合わせです。暗号スイートは、接続が暗号化されているかどうか、コンテンツの整合性が検証されているかどうか、および認証がどのように行われるかを定義します。

SSLServerSocketFactory

javax.net.ssl.SSLServerSocketFactoryクラスのメソッドは、と同じ3つのカテゴリに分類されSSLSocketFactoryます。まず、デフォルトのSSLサーバーソケットファクトリを取得する単一の静的メソッドがありますstatic ServerSocketFactory getDefault()

SSLサーバーソケットを返すメソッドは、java.net.ServerSocketクラスで見つかったコンストラクターを反映しています。

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

次のコマンドを使用してクライアントを起動します(foobarトラストストアファイルの名前とそのパスワードの両方です)。

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