第 II 部:Java SE 6 Security API を使用した安全な通信

ここでは、安全な通信を実行するアプリケーションを構築する方法について説明します。Java SE 6 プラットフォームは、アプリケーションが安全な通信を実行できるようにする 3 つの標準 API を提供します。Java Generic Security Service (GSS)、Java SASL API、および Java Secure Socket Extension (JSSE) です。アプリケーションを構築する場合、これらの API のうちのどれを使用すればよいでしょうか。その答えは、プロトコルまたはサービスの要件、配備インフラストラクチャー、ほかのセキュリティーサービスとの統合など、数多くの要因によって異なります。たとえば、LDAP クライアントライブラリを構築する場合は、Java SASL API を使用する必要があります。SASL の使用は、LDAP のプロトコル定義の一部であるためです。別の例として、サービスが SSL をサポートする場合、サービスにアクセスしようとするクライアントアプリケーションは JSSE を使用する必要があります。

課題 3:Java Generic Security Service (GSS) API の使用


この課題の目標:


この課題の目標は、Java GSS API を使用して安全な認証および通信を実行する方法について学習することです。

この課題の内容:

Generic Security Service API は、認証、メッセージの整合性、メッセージの機密性などのさまざまなセキュリティーサービスにアクセスするための、統一された C 言語インタフェースを提供します。Java GSS API は、対応するインタフェースを Java アプリケーションに提供します。この API を使用すると、アプリケーションは認証を実行してピアとの安全な通信を確立できます。GSS-API および Java GSS-API によってアクセスされるもっとも一般的なセキュリティーサービスの 1 つは、Kerberos です。

この課題のリソース:

  1. JAAS および Java GSS-API チュートリアル
  2. 「Generic Security Service API Version 2:Java Bindings」(RFC 2853)
  3. Java GSS javadoc:org.ietf.jgss

この課題の概要:

この課題では、Java GSS API を使用して安全に通信する方法を示すクライアントサーバーアプリケーションを扱います。クライアント部分とサーバー部分は、課題 1 に示されているように、最初に Kerberos への認証を行います。これにより、資格が被認証者 (subject) に格納されます。アプリケーションは、被認証者を使用して Subject.doAs の内部で、(基盤となる GSS 機構として Kerberos を使用して) Java GSS 操作を実行するアクションを実行します。Java GSS Kerberos 機構は doAs の内部で実行されるため、Kerberos 資格を被認証者から取得し、それらを使用してピアとの認証およびメッセージの安全な交換を行います。

実行手順:

  1. 次のコードを参照してください。これは src/GssServer.java にあります。

    このコードは、サービス主体が KDC に対して認証した後に実行するアクションを定義します。このコードによって、課題 1 の行 11 の MyAction が置き換えられます。強調表示されている行に注目してください。このコードは、最初に GSSManager のインスタンスを作成し (行 8)、それを使用して独自の資格を取得して (行 10 ~ 11) GSSContext のインスタンスを作成します (行 18)。このコンテキストを使用して認証を実行します (行 22 ~ 34 のループ)。認証が完了すると、暗号化された入力をクライアントから受け入れ、確立されたセキュリティーコンテキストを使用してデータを復号化します (行 45)。次に、セキュリティーコンテキストを使用して元の入力と日付を含む応答を暗号化し (行 49)、クライアントに返信します。

GssServer.java のコードリスト



  1. static class GssServerAction implements PrivilegedExceptionAction {
  2. ...
  3.   public Object run() throws Exception {
  4.     // Create server socket for accepting connections
  5.     ServerSocket ss = new ServerSocket(localPort);

  6.     // Get own Kerberos credentials for accepting connection
  7.     GSSManager manager = GSSManager.getInstance();
  8.     Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
  9.     GSSCredential serverCreds = manager.createCredential(null,
  10.       GSSCredential.DEFAULT_LIFETIME, krb5Oid, GSSCredential.ACCEPT_ONLY);

  11.     while( true ) {
  12.       Socket socket = ss.accept();
  13.       DataInputStream inStream = new DataInputStream(socket.getInputStream());
  14.       DataOutputStream outStream = new DataOutputStream(socket.getOutputStream());

  15.       GSSContext context = manager.createContext((GSSCredential)serverCreds);

  16.       // Do the context establishment loop
  17.       byte[] token = null;
  18.       while (!context.isEstablished()) {
  19.         // Read token
  20.         token = new byte[inStream.readInt()];
  21.         inStream.readFully(token);

  22.         // Process token
  23.         token = context.acceptSecContext(token, 0, token.length);

  24.         // Send a token to the peer if one was generated by acceptSecContext
  25.         outStream.writeInt(token.length);
  26.         outStream.write(token);
  27.         outStream.flush();
  28.       }

  29.       // Context established!

  30.       // Create MessageProp for use with unwrap (will be set upon return from unwrap)
  31.       MessageProp prop = new MessageProp(0, false);

  32.       // Read token from client
  33.       token = new byte[inStream.readInt()];
  34.       inStream.readFully(token);
  35.       // Unwrap (decrypt) token sent by client
  36.       byte[] input = context.unwrap(token, 0, token.length, prop);
  37.       ...
  38.       // Create new token and send to client
  39.       byte[] reply = ...;
  40.       token = context.wrap(reply, 0, reply.length, prop);

  41.       outStream.writeInt(token.length);
  42.       outStream.write(token);
  43.       outStream.flush();
  44.       context.dispose();
  45.       socket.close();
  46.     }
  47.   }
  48. }


  1. サンプルコードをコンパイルします。
    % javac GssServer.java
  2. 次のコードを参照してください。これは src/GssClient.java にあります。

    このコードは、クライアント主体が KDC に対して認証した後に実行するアクションを定義します。このコードによって、課題 1 の行 11 の MyAction が置き換えられます。強調表示されている行に注目してください。このコードは、最初に GSSManager のインスタンスを作成し (行 10)、それを使用して通信対象のサービスの主体名を取得します (行 12)。次に、GSSContext のインスタンスを作成し (行 15、16)]、サービスとの認証を行います (行 22 ~ 33 のループ)。認証が完了すると、確立されたセキュリティーコンテキストを使用してメッセージを暗号化し (行 42)、サーバーに送信します。次に、暗号化されたメッセージをサーバーから読み取り、確立されたセキュリティーコンテキストを使用して復号化します (行 53)。
GssClient.java のコードリスト

  1. static class GssClientAction implements PrivilegedExceptionAction {
  2. ...
  3.   public Object run() throws Exception {
  4.     // Create socket to server
  5.     Socket socket = new Socket(hostName, port);
  6.     DataInputStream inStream = new DataInputStream(socket.getInputStream());
  7.     DataOutputStream outStream = new DataOutputStream(socket.getOutputStream());

  8.     // Get service's principal name
  9.     GSSManager manager = GSSManager.getInstance();
  10.     Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
  11.     GSSName serverName = manager.createName(serverPrinc, GSSName.NT_HOSTBASED_SERVICE);

  12.     // Get the context for authentication
  13.     GSSContext context = manager.createContext(serverName, krb5Oid, null,
  14.        GSSContext.DEFAULT_LIFETIME);
  15.     context.requestMutualAuth(true); // Request mutual authentication
  16.     context.requestConf(true); // Request confidentiality

  17.     // Do the context establishment loop
  18.     byte[] token = new byte[0];
  19.     while (!context.isEstablished()) {
  20.       token = context.initSecContext(token, 0, token.length);
  21.       outStream.writeInt(token.length);
  22.       outStream.write(token);
  23.       outStream.flush();

  24.       // Check if we're done
  25.       if (!context.isEstablished()) {
  26.         token = new byte[inStream.readInt()];
  27.         inStream.readFully(token);
  28.       }
  29.     }

  30.     // Context established!

  31.     // Create MessageProp for use with unwrap (true means request confidentiality)
  32.     MessageProp prop = new MessageProp(0, true);

  33.     // Create encrypted message and send to server
  34.     byte[] reply = ...;
  35.     token = context.wrap(reply, 0, reply.length, prop);

  36.     outStream.writeInt(token.length);
  37.     outStream.write(token);
  38.     outStream.flush();

  39.     // Read token from server
  40.     token = new byte[inStream.readInt()];
  41.     inStream.readFully(token);

  42.     // Unwrap (decrypt) token sent by server
  43.     byte[] input = context.unwrap(token, 0, token.length, prop);
  44.     ...
  45.     context.dispose();
  46.     socket.close();
  47.     return null;
  48.   }
  49. }


  1. サンプルコードをコンパイルします。
    % javac GssClient.java
    
  2. 新規のウィンドウを起動してサーバーを起動します。
    % xterm &
    % java -Djava.security.auth.login.config=jaas-krb5.conf \
           GssServer
    
  3. クライアントアプリケーションを実行します。GssClient は、次の 2 つのパラメータを取ります。サービス名、およびそのサービスが実行されているサーバーの名前です。たとえば、サービスがマシン j1hol-001 上で実行されている host の場合は、次を入力します。パスワードの入力を求められた場合は、changeit を入力します。
        
    % java -Djava.security.auth.login.config=jaas-krb5.conf \
           GssClient host j1hol-001
    
  4. クライアントとサーバーのアプリケーションウィンドウに次の出力が表示されます。

GssServer の例を実行した場合の出力


  1. Authenticated principal:[host/j1hol-001@J1LABS.EXAMPLE.COM]
  2. Waiting for incoming connections...
  3. Got connection from client /129.145.128.102
  4. Context Established!
  5. Client principal is test@J1LABS.EXAMPLE.COM
  6. Server principal is host/j1hol-001@J1LABS.EXAMPLE.COM
  7. Mutual authentication took place!
  8. Received data "Hello There!" of length 12
  9. Confidentiality applied:true
  10. Sending:Hello There!Thu May 06 12:11:15 PDT 2005

GssClient の例を実行した場合の出力


  1. Kerberos password for test:changeit
  2. Authenticated principal:[test@J1LABS.EXAMPLE.COM]
  3. Connected to address j1hol-001/129.145.128.102
  4. Context Established!
  5. Client principal is test@J1LABS.EXAMPLE.COM
  6. Server principal is host@j1hol-001
  7. Mutual authentication took place!
  8. Sending message:Hello There!
  9. Will read token of size 93
  10. Received message:Hello There!Thu May 06 12:11:15 PDT 2005

まとめ:

この課題では、Java GSS API を使用してお互い安全に認証および通信を行うクライアント/サーバーアプリケーションを記述する方法について学習しました。

次の手順

  1. 課題 4 に進み、Java SASL API を使用してお互い安全に認証および通信を行うクライアント/サーバーアプリケーションを記述する方法について学習します。
  2. 課題 5 に進み、JSSE を使用してお互い安全に認証および通信を行うクライアント/サーバーアプリケーションを記述する方法について学習します。
  3. 課題 6 に進み、Kerberos 環境でシングルサインオンを行う場合に使用したサンプルプログラムを構成する方法について学習します。

課題 4:Java SASL API の使用

この課題の目標:

この課題の目標は、Java SASL API を使用して安全な認証および通信を実行する方法について学習することです。

この課題の内容:

Simple Authentication and Security Layer (SASL) は、チャレンジ応答プロトコルを指定します。このプロトコルでは、認証および (オプションの) 後続の通信を行うためのセキュリティー層を確立するために、クライアントとサーバー間でデータが交換されます。SASL では異なる「機構」を使用できます。このような機構はそれぞれ、交換されるデータと名前を定義するプロファイルによって識別されます。SASL は、LDAPv3 や IMAPv4 などの接続ベースのプロトコルとともに使用されます。SASL については、RFC 4422 を参照してください。

Java SASL API は、機構に依存しない方法で SASL を使用するアプリケーション用の API を定義します。たとえば、SASL を使用するネットワークプロトコルのライブラリを記述する場合、Java SASL API を使用して、ピアと交換するデータを生成できます。ライブラリが配備されるときに、ライブラリで使用する機構を動的に構成できます。

認証以外に、SASL を使用して認証後に使用されるセキュリティー層とのネゴシエーションを行うことができます。ただし、GSS-API とは異なり、セキュリティー層のプロパティー (整合性を求めるか機密性を求めるかなど) はネゴシエーション時に決定されます。GSS-API では、メッセージごとに機密性をオンまたはオフにすることができます。

この課題のリソース:

  1. Java SASL API プログラミングおよび配備ガイド
  2. Java SASL API javadoc
  3. Simple Authentication and Security Layer (SASL) (RFC 4422)

この課題の概要:

この課題では、Java SASL API を使用して安全に通信する方法を示すクライアントサーバーアプリケーションを扱います。クライアント部分とサーバー部分は、課題 1 を使用して最初に Kerberos への認証を行います。これにより、資格が被認証者に格納されます。アプリケーションは、被認証者を使用して Subject.doAs の内部で、(基盤となる SASL 機構として Kerberos を使用して) Java SASL API 操作を実行するアクションを実行します。SASL/Kerberos 機構は、doAs の内部で実行されるため、Kerberos 資格を被認証者から取得し、それらを使用してピアとの認証およびメッセージの安全な交換を行います。

この例は、AppConnection クラスによって実装される単純なプロトコルを使用します。このプロトコルは、認証コマンドおよびデータコマンドを交換します。各コマンドは、タイプ (AppConnection.AUTH_CMD など)、後続のデータの長さ、およびデータ自体で構成されています。データが認証または暗号化された/整合性が保護されたアプリケーションデータ用の場合、そのデータは SASL バッファーです。それ以外の場合は、プレーンなアプリケーションデータです。

実行手順:

  1. 次のコードを参照してください。これは src/SaslTestServer.java にあります。

    このコードは、サービス主体が KDC に対して認証した後に実行するアクションを定義します。このコードによって、課題 1 の行 11 の MyAction が置き換えられます。強調表示されている行に注目してください。サーバーは、サポートする保護の品質を指定し (行 9)、SaslServer のインスタンスを作成して認証を実行します (行 21)。SASL のチャレンジ応答プロトコルは、 while ループ (行 33 ~ 49) の中で実行されています。そこでサーバーは、チャレンジをクライアントに送信しクライアントからの応答を処理します。認証後、認証されたクライアントのアイデンティティーを getAuthorizedID() の呼び出しによって取得できます (行 61)。セキュリティー層がネゴシエーションされた場合、サーバーはクライアントと安全にデータを交換できます (行 66、70)。

SaslTestServer.java のコードリスト


  1. static class SaslServerAction implements PrivilegedExceptionAction {
  2. ...
  3.   public Object run() throws Exception {
  4.     // Create server socket for accepting connections
  5.     ServerSocket ss = new ServerSocket(localPort);

  6.     // Support all quality-of-protection options
  7.     HashMap<String,Object> props = new HashMap<String,Object>();
  8.     props.put(Sasl.QOP, "auth-conf,auth-int,auth");

  9.     while( true ) {
  10.       // Create application-level connection to handle request
  11.       Socket socket = ss.accept();
  12.       AppConnection conn = new AppConnection(socket);

  13.       // Normally, the application protocol will negotiate which
  14.       // SASL mechanism to use.In this simplified example, we
  15.       // will always use "GSSAPI" (the name of the mechanism that does Kerberos via GSS-API)

  16.       // Create SaslServer to perform authentication
  17.       SaslServer srv = Sasl.createSaslServer("GSSAPI", service, serviceName, props, cbh);

  18.       if (srv == null) {
  19.         // ... handle error
  20.       }

  21.       // Read the initial response from client
  22.       byte[] response = conn.receive(AppConnection.AUTH_CMD);
  23.       AppConnection.AppReply clientMsg;
  24.       boolean auth = false;

  25.       // Perform authentication
  26.       while (!srv.isComplete()) {
  27.         try
  28.           // Generate challenge based on response
  29.           byte[] challenge = srv.evaluateResponse(response);
  30.           if (srv.isComplete()) {
  31.             conn.send(AppConnection.SUCCESS, challenge);
  32.             auth = true;
  33.           } else {
  34.             clientMsg = conn.send(AppConnection.AUTH_IN_PROGRESS, challenge);
  35.             response = clientMsg.getBytes();
  36.           }
  37.         } catch (SaslException e) {
  38.           // Send failure notification to client
  39.           conn.send(AppConnection.FAILURE, null);
  40.           break;
  41.         }
  42.       }

  43.       // Authentication completed!

  44.       // Check status of authentication
  45.       if (srv.isCompleted() && auth) {
  46.         System.out.println("authorized client is:" + srv.getAuthorizationID());
  47.       } else {
  48.         // Report failure ...
  49.       }

  50.       // Find out whether security layer was negotiated
  51.       String qop = (String) srv.getNegotiatedProperty(Sasl.QOP);
  52.       boolean sl = (qop.equals("auth-conf") || qop.equals("auth-int"));

  53.       // Read and decrypt message from client
  54.       byte[] msg = conn.receive(AppConnection.DATA_CMD);
  55.       byte[] realMsg = (sl ? srv.unwrap(msg, 0, msg.length) :msg);
  56.       ...
  57.       // Create and encrypt message to send to client
  58.       byte[] reply = ...;
  59.       byte[] realReply = (sl ? srv.wrap(reply, 0, reply.length) :reply);
  60.       conn.send(AppConnection.SUCCESS, realReply);
  61.     }
  62.   }
  63. }


  1. サンプルコードをコンパイルします。
    % javac SaslTestServer.java
  2. 次のコードを参照してください。これは src/SaslTestClient.java にあります。このコードは、クライアント主体が KDC に対して認証した後に実行するアクションを定義します。このコードによって、課題 1 の行 11 の MyAction が置き換えられます。強調表示されている行に注目してください。プログラムは、必要な保護の品質 (この場合は、機密性) を最初に指定し (行 8)、次に SaslClient のインスタンスを作成して認証に使用します (行 11 ~ 12)。次に、機構に初期応答があるかどうかをチェックし、ある場合は、空のバイト配列で evaluateChallenge() を呼び出すことによって応答を取得します (行 20)。次に、応答をサーバーに送信して認証を開始します。SASL のチャレンジ応答プロトコルは、while ループ (行 24 ~ 39) の中で実行されています。そこでクライアントは、サーバーから取得したチャレンジを評価してチャレンジに対応する応答をサーバーに送信します。認証後、クライアントは、ネゴシエーションされたセキュリティー層を使用してサーバーとの通信に進むことができます (行 48、55)。

SaslTestClient.java のコードリスト


  1. static class SaslClientAction implements PrivilegedExceptionAction {
  2. ...
  3.   public Object run() throws Exception {
  4.     // Create application-level connection
  5.     AppConnection conn = new AppConnection(serverName, port);

  6.     HashMap<String,Object> props = new HashMap<String,Object>();
  7.     props.put(Sasl.QOP, "auth-conf"); // Request confidentiality

  8.     // Create SaslClient to perform authentication
  9.     SaslClient clnt = Sasl.createSaslClient(
  10.       new String[]{"GSSAPI"}, null, service, serverName, props, cbh);
  11.     if (clnt == null) {
  12.       // ... handle error
  13.     }

  14.     byte[] challenge;

  15.     // Get initial response for authentication
  16.     byte[] response = clnt.hasInitialResponse() ? clnt.evaluateChallenge(EMPTY) :EMPTY;
  17.     AppConnection.AppReply reply = conn.send(AppConnection.AUTH_CMD, response);

  18.     // Repeat until authentication terminates
  19.     while (!clnt.isComplete() &&
  20.       (reply.getStatus() == AppConnection.AUTH_INPROGRESS ||
  21.        reply.getStatus() == AppConnection.SUCCESS))
  22.       // Evaluate challenge to generate response
  23.       challenge = reply.getBytes()
  24.       response = clnt.evaluateChallenge(challenge)

  25.       if (reply.getStatus() == AppConnection.SUCCESS) {
  26.         if (response != null) {
  27.           throw new Exception("Protocol error")
  28.         }
  29.         break;
  30.       }
  31.       // Send response to server and read server's next challenge
  32.       reply = conn.send(AppConnection.AUTH_CMD, response);
  33.     }
  34.     // Authentication completed!
  35.     // Find out whether security layer was negotiated
  36.     String qop = (String) srv.getNegotiatedProperty(Sasl.QOP);
  37.     boolean sl = (qop.equals("auth-conf") || qop.equals("auth-int"));

  38.     byte[] msg = ...;

  39.     // Create and send encrypted data to server
  40.     byte[] encrypted = (sl ? clnt.wrap(msg, 0, msg.length) :msg);
  41.     reply = conn.send(AppConnection.DATA_CMD, encrypted);) {

  42.     ...

  43.     // Read and decrypt data from server
  44.     byte[] encryptedReply = reply.getBytes();
  45.     byte[] clearReply = (sl ? clnt.unwrap(encryptedReply, 0, encryptedReply.length)
  46.       : encryptedReply);
  47.     conn.close();
  48.     return null;
  49.   }
  50. }

  1. サンプルコードをコンパイルします。
    % javac SaslTestClient.java
  2. 新規のウィンドウを起動してサーバーを起動します。SaslTestServer は、次の 2 つのパラメータを取ります。サービス名、およびそのサービスが実行されているサーバーの名前です。たとえば、サービスがマシン j1hol-001 上で実行されている host の場合は、次を入力します。
    % xterm &
    % java
    -Djava.security.auth.login.config=jaas-krb5.conf \
    SaslTestServer host j1hol-001

  3. クライアントアプリケーションを実行します。SaslTestClient は、次の 2 つのパラメータを取ります。サービス名、およびそのサービスが実行されているサーバーの名前です。たとえば、サービスがマシン j1hol-001 上で実行されている host の場合は、次を入力します。パスワードの入力を求められた場合は、changeit を入力します。

    % java -Djava.security.auth.login.config=jaas-krb5.conf \
    SaslTestClient host j1hol-001
  4. クライアントとサーバーのアプリケーションウィンドウに次の出力が表示されます。

SaslTestServer の例を実行した場合の出力


  1. Authenticated principal:[host/j1hol-001@J1LABS.EXAMPLE.COM]
  2. Waiting for incoming connections...
  3. Got connection from client /129.145.128.102
  4. Client authenticated; authorized client is:test@J1LABS.EXAMPLE.COM
  5. Negotiated QOP:auth-conf
  6. Received:Hello There!
  7. Sending:Hello There!Fri May 07 15:32:37 PDT 2005
  8. Received data "Hello There!" of length 12

SaslTestClient の例を実行した場合の出力


  1. Kerberos password for test:changeit
  2. Authenticated principal:[test@J1LABS.EXAMPLE.COM]
  3. Connected to address j1hol-001/129.145.128.102
  4. Client authenticated.
  5. Negotiated QOP:auth-conf
  6. Sending:Hello There!
  7. Received:Hello There!Fri May 07 15:32:37 PDT 2005

  1. 異なる保護の品質を使用してプログラムを試行するには、SaslTestClient の行 8 を変更します。たとえば、行 8 を次の行で置き換えて、整合性保護を使用します (機密性なし)。
    props.put(Sasl.QOP, "auth-int");

まとめ:

この課題では、Java SASL API を使用してお互い安全に認証および通信を行うクライアント/サーバーアプリケーションを記述する方法について学習しました。

次の手順

  1. 課題 5 に進み、JSSE を使用してお互い安全に認証および通信を行うクライアント/サーバーアプリケーションを記述する方法について学習します。
  2. 課題 6 に進み、Kerberos 環境でシングルサインオンを行う場合に使用したサンプルプログラムを構成する方法について学習します。

課題 5:Kerberos での Java Secure Socket Extension の使用

この課題の目標:

この課題の目標は、JSSE API を使用して、Kerberos 暗号化方式を使用した安全な認証および通信を実行する方法について学習することです。

この課題の内容:

Secure Socket Layer (SSL) と Transport Layer Security (TLS) は、インターネットで暗号化を実装する場合にもっともよく使用されるプロトコルです。TLS は、SSL から発展したインターネット標準です。SSL/TLS は、アプリケーションレベルのプロトコル (HTTP や LDAP など) に安全な認証と通信を提供します。たとえば、HTTPS は SSL/TLS を介して HTTP を使用した結果のプロトコルです。SSL/TLS は、HTTP などの標準プロトコルに対して使用されるだけでなく、安全に通信する必要がある、カスタムプロトコルを使用するカスタムアプリケーションを構築する場合にも広く使用されます。

SSL/TLS は、従来は証明書ベースの認証を使用しており、一般にサーバー認証に使用されます。たとえば、ブラウザなどの Web クライアントがユーザーに代わって安全な Web サイト (サーバー) にアクセスすると、サーバーは、ブラウザがサーバーのアイデンティティーを検証できるようにサーバーの証明書をブラウザに送信します。これにより、ユーザーが機密情報 (クレジットカード情報など) を偽のサーバーに公開することはありません。最近、新しい標準によって、TLS で Kerberos を使用できるようになりました。つまり、証明書ベースの認証を使用する代わりに、アプリケーションは Kerberos 資格を使用して、配備環境で Kerberos インフラストラクチャーを利用できます。Kerberos 暗号化方式を使用すると、サーバーだけでなくクライアントも認証される相互認証が自動的にサポートされます。

特定のアプリケーションで Java GSS、Java SASL、または JSSE を使用するかどうかの決定は、複数の要因に依存する場合があります。アプリケーションが相互に作用するサービス (によって使用されているプロトコル)、配備環境 (PKI または Kerberos ベース)、アプリケーションのセキュリティー要件などです。JSSE は、I/O およびトランスポートを行う安全な終端間チャネルを提供します。一方、Java GSS および Java SASL は、データの暗号化および整合性保護を提供しますが、安全なデータをピアにトランスポートするのはアプリケーションです。JSSE と Java GSS をいつ使用するかを決定する要因の詳細の一部が、ドキュメント「Java GSS-API および JSSE をいつ使用するか」に示されています。

この課題のリソース:

  1. Java Secure Socket Extension (JSSE) リファレンスガイド
  2. JSSE javadoc:javax.net および javax.net.ssl
  3. The SSL Protocol version 3.0
  4. The TLS Protocol Version 1.0 (RFC 2246)
  5. Addition of Kerberos Cipher Suites to Transport Layer Security TLS (RFC 2712)
  6. Java GSS-API および JSSE をいつ使用するか

この課題の概要:

この課題では、JSSE および Kerberos 暗号化方式を使用して安全に通信する方法を示すクライアントサーバーアプリケーションを扱います。クライアント部分とサーバー部分は、課題 1 を使用して最初に Kerberos への認証を行います。これにより、資格が被認証者に格納されます。アプリケーションは、被認証者を使用して Subject.doAs の内部で、(Kerberos 暗号化方式を使用して) JSSE 操作を実行するアクションを実行します。Kerberos 暗号化方式の実装は doAs の内部で実行されるため、Kerberos 資格を被認証者から取得し、それらを使用してピアとの認証およびメッセージの安全な交換を行います。この例では、改行で終了されるメッセージ (ネゴシエーションが行われた暗号群を使用して暗号化され、整合性が保護される) を、クライアントとサーバー間で送受信します。

標準 (RFC 2712) に従って、Kerberos が有効なすべての TLS アプリケーションは、同じサービス名、つまり「host」を使用します。このため、この課題では、Kerberos サービス名を明示的に指定する必要はありません。

実行手順:

  1. 次のコードを参照してください。これは、src/JsseServer.java にあります。

    このコードは、サービス主体が KDC に対して認証した後に実行するアクションを定義します。このコードによって、課題 1 の行 11 の MyAction が置き換えられます。強調表示されている行に注目してください。サーバーは最初に、SSLServerSocket を作成します (行 5 ~ 8)。これは、SSLServerSocket が必要に応じて自動的な認証、暗号化、および復号化を提供する点を除いて、プレーンな ServerSocket を作成するアプリケーションと似ています。次に、サーバーは使用する暗号群を設定します (行 11 ~ 12)。次に、サーバーはループで実行され、SSL クライアントからの接続を受け入れ (行 17)、SSL ソケットから読み書きします (行 23、28)。サーバーは、getLocalPrincipal() および getPeerPrincipal() メソッドを呼び出すことによって (行 32 ~ 33)、ソケットの所有者のアイデンティティーを調べることができます。

JsseServer.java のコードリスト


  1. static class JsseServerAction implements PrivilegedExceptionAction {
  2. ...
  3.   public Object run() throws Exception {
  4.     // Create TLS socket for accepting connections
  5.     SSLServerSocketFactory sslssf =
  6.       (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
  7.     SSLServerSocket sslServerSocket =
  8.       (SSLServerSocket) sslssf.createServerSocket(localPort);

  9.     // Enable only a Kerberos cipher suite
  10.     String enabledSuites[] = { "TLS_KRB5_WITH_3DES_EDE_CBC_SHA" };
  11.     sslServerSocket.setEnabledCipherSuites(enabledSuites);
  12.     // Should handle exception if enabledSuites is not supported

  13.     while( true ) {
  14.       // Create socket to handle request
  15.       SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
  16.       BufferedReader in = new BufferedReader(new InputStreamReader(
  17.         sslSocket.getInputStream()));
  18.       BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
  19.         sslSocket.getOutputStream()));

  20.       String inStr = in.readLine();
  21.       // ... use inStr

  22.       // Compose and send reply
  23.       String outStr = inStr + " " + new Date().toString() + "\n";
  24.       out.write(outStr);
  25.       out.flush();

  26.       // Get names of principal at both ends of secure connection
  27.       Principal self = sslSocket.getSession().getLocalPrincipal();
  28.       Principal peer = sslSocket.getSession().getPeerPrincipal();

  29.       sslSocket.close();
  30.     }
  31.   }
  32. }


  1. サンプルコードをコンパイルします。

    % javac JsseServer.java
  2. 次のコードを参照してください。これは src/JsseClient.java にあります。このコードは、クライアント主体が KDC に対して認証した後に実行するアクションを定義します。このコードによって、課題 1 の行 11 の MyAction が置き換えられます。強調表示されている行に注目してください。クライアントは最初に、SSLSocket を作成します。次に、クライアントは使用する暗号群を設定します (行 11 ~ 12)。次に、クライアントは、SSLSocket を使用してソケットの入力/出力ストリームを読み書きすることによって、サーバーとメッセージを交換します。クライアントは、getLocalPrincipal() および getPeerPrincipal() メソッドを呼び出すことによって (行 26 ~ 27)、ソケットの所有者のアイデンティティーを調べることができます。

JsseClient.java のコードリスト


  1. static class JsseClientAction implements PrivilegedExceptionAction {
  2. ...
  3.   public Object run() throws Exception {
  4.     // Create SSL connection
  5.     SSLSocketFactory sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();
  6.     SSLSocket sslSocket = (SSLSocket) sslsf.createSocket(server, port);

  7.     // Enable only a Kerberos cipher suite
  8.     String enabledSuites[] = { "TLS_KRB5_WITH_3DES_EDE_CBC_SHA" };
  9.     sslSocket.setEnabledCipherSuites(enabledSuites);
  10.     // Should handle exception if enabledSuites is not supported

  11.     BufferedReader in = new BufferedReader(new InputStreamReader(
  12.       sslSocket.getInputStream()));
  13.     BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
  14.       sslSocket.getOutputStream()));

  15.     String outStr = ...;
  16.     out.write(outStr);
  17.     out.flush();

  18.     String inStr = in.readLine();
  19.     // ... use inStr

  20.     // Get names of principal at both ends of secure connection
  21.     Principal self = sslSocket.getSession().getLocalPrincipal();
  22.     Principal peer = sslSocket.getSession().getPeerPrincipal();

  23.     sslSocket.close();
  24.     return null;
  25.   }
  26. }


  1. サンプルコードをコンパイルします。
    % javac JsseClient.java
  2. 新規のウィンドウを起動してサーバーを起動します。JsseServer は、パラメータを 1 つ取ります。JSSE サービスが実行されているサーバーの名前です。たとえば、マシン j1hol-001 上で実行されている場合は、次のように入力します。
    % xterm &
    % java
    -Djava.security.auth.login.config=jaas-krb5.conf \
    JsseServer j1hol-001

  3. クライアントアプリケーションを実行します。JsseClient は、パラメータを 1 つ取ります。JSSE サービスが実行されているサーバーの名前です。たとえば、サービスがマシン j1hol-001 上で実行されている場合は、次のように入力します。パスワードの入力を求められた場合は、changeit を入力します。

    % java -Djava.security.auth.login.config=jaas-krb5.conf \
    JsseClient j1hol-001
  4. クライアントとサーバーのアプリケーションウィンドウに次の出力が表示されます。

JsseServer の例を実行した場合の出力


  1. Authenticated principal:[host/j1hol-001@J1LABS.EXAMPLE.COM]
  2. Waiting for incoming connections...
  3. Got connection from client /129.145.128.102
  4. Received:Hello There!
  5. Sending:Hello There!Fri May 07 15:32:37 PDT 2005
  6. Cipher suite in use:TLS_KRB5_WITH_3DES_EDE_CBC_SHA
  7. I am:host/j1hol-001@J1LABS.EXAMPLE.COM
  8. Client is:test@J1LABS.EXAMPLE.COM

JsseClient の例を実行した場合の出力


  1. Kerberos password for test:changeit
  2. Authenticated principal:[test@J1LABS.EXAMPLE.COM]
  3. Sending:Hello There!
  4. Received:Hello There!Fri May 07 15:32:37 PDT 2005
  5. Cipher suite in use:TLS_KRB5_WITH_3DES_EDE_CBC_SHA
  6. I am:test@J1LABS.EXAMPLE.COM
  7. Server is:host/j1hol-001@J1LABS.EXAMPLE.COM

まとめ:

この課題では、基盤となる認証システムとして Kerberos を使用し、JSSE を使用してお互い安全に認証および通信を行うクライアント/サーバーアプリケーションを記述する方法について学習しました。

次の手順

  1. 課題 6 に進み、課題 3、4、および 5 のサンプルプログラムを構成して Kerberos 環境でシングルサインオンを行う方法について学習します。