第 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 です。
この課題のリソース:
- JAAS および Java GSS-API チュートリアル
 
- 「Generic Security Service API Version 2:Java Bindings」(RFC 2853) 
 
- Java GSS javadoc:org.ietf.jgss
  
 
この課題の概要:
この課題では、Java GSS API を使用して安全に通信する方法を示すクライアントサーバーアプリケーションを扱います。クライアント部分とサーバー部分は、課題 1 に示されているように、最初に Kerberos への認証を行います。これにより、資格が被認証者に格納されます。アプリケーションは、被認証者を使用して Subject.doAs の内部で、(基盤となる GSS 機構として Kerberos を使用して) Java GSS 操作を実行するアクションを実行します。Java GSS Kerberos 機構は doAs の内部で実行されるため、Kerberos 資格を被認証者から取得し、それらを使用してピアとの認証およびメッセージの安全な交換を行います。
実行手順:
-  次のコードを参照してください。これは 
src/GssServer.java にあります。
    
このコードは、サービス主体が KDC に対して認証した後に実行するアクションを定義します。 It replaces the MyAction of line 11 of Exercise 1.強調表示されている行に注目してください。このコードは、最初に GSSManager のインスタンスを作成し (行 8)、それを使用して独自の資格を取得して (行 10 ~ 11) GSSContext のインスタンスを作成します (行 18)。このコンテキストを使用して認証を実行します (行 22 ~ 34 のループ)。認証が完了すると、暗号化された入力をクライアントから受け入れ、確立されたセキュリティーコンテキストを使用してデータを復号化します (行 45)。次に、セキュリティーコンテキストを使用して元の入力と日付を含む応答を暗号化し (行 49)、クライアントに返信します。 
   
GssServer.java のコードリスト
 
  
    
        
      
static class GssServerAction implements PrivilegedExceptionAction {  
        ...  
  public Object run() throws Exception { 
    // Create server socket for accepting connections 
    ServerSocket ss = new ServerSocket(localPort); 
         
         
    // Get own Kerberos credentials for accepting connection  
    GSSManager manager = GSSManager.getInstance(); 
    Oid krb5Oid = new Oid("1.2.840.113554.1.2.2"); 
    GSSCredential serverCreds = manager.createCredential(null, 
      GSSCredential.DEFAULT_LIFETIME, krb5Oid, GSSCredential.ACCEPT_ONLY); 
         
         
    while( true ) { 
      Socket socket = ss.accept(); 
      DataInputStream inStream = new DataInputStream(socket.getInputStream()); 
      DataOutputStream outStream = new DataOutputStream(socket.getOutputStream()); 
         
         
      GSSContext context = manager.createContext((GSSCredential)serverCreds); 
         
         
      // Do the context establishment loop 
      byte[] token = null; 
      while (!context.isEstablished()) { 
        // Read token 
        token = new byte[inStream.readInt()]; 
        inStream.readFully(token); 
         
         
        // Process token 
        token = context.acceptSecContext(token, 0, token.length); 
         
         
        // Send a token to the peer if one was generated by acceptSecContext 
        outStream.writeInt(token.length); 
        outStream.write(token); 
        outStream.flush(); 
              } 
         
         
      // Context established! 
         
         
      // Create MessageProp for use with unwrap (will be set upon return from unwrap) 
      MessageProp prop = new MessageProp(0, false); 
         
         
      // Read token from client 
      token = new byte[inStream.readInt()]; 
      inStream.readFully(token); 
      // Unwrap (decrypt) token sent by client 
      byte[] input = context.unwrap(token, 0, token.length, prop); 
              ... 
      // Create new token and send to client 
      byte[] reply = ...; 
      token = context.wrap(reply, 0, reply.length, prop); 
         
         
      outStream.writeInt(token.length); 
      outStream.write(token); 
      outStream.flush(); 
      context.dispose(); 
      socket.close(); 
            } 
          } 
        } 
       
       
       | 
    
  
- サンプルコードをコンパイルします。 
    
% javac GssServer.java
      
- 次のコードを参照してください。これは 
src/GssClient.java にあります。 
このコードは、クライアント主体が KDC に対して認証した後に実行するアクションを定義します。 It replaces the MyAction of line 11 of Exercise 1.強調表示されている行に注目してください。このコードは、最初に GSSManager のインスタンスを作成し (行 10)、それを使用して通信対象のサービスの主体名を取得します (行 12)。次に、GSSContext のインスタンスを作成し (行 15、16)]、サービスとの認証を行います (行 22 ~ 33 のループ)。認証が完了すると、確立されたセキュリティーコンテキストを使用してメッセージを暗号化し (行 42)、サーバーに送信します。次に、暗号化されたメッセージをサーバーから読み取り、確立されたセキュリティーコンテキストを使用して復号化します (行 53)。  
GssClient.java のコードリスト
  
    
        
      
static class GssClientAction implements PrivilegedExceptionAction {  
        ...  
  public Object run() throws Exception { 
    // Create socket to server 
    Socket socket = new Socket(hostName, port); 
    DataInputStream inStream = new DataInputStream(socket.getInputStream()); 
    DataOutputStream outStream = new DataOutputStream(socket.getOutputStream()); 
         
         
    // Get service's principal name  
    GSSManager manager = GSSManager.getInstance(); 
    Oid krb5Oid = new Oid("1.2.840.113554.1.2.2"); 
    GSSName serverName = manager.createName(serverPrinc, GSSName.NT_HOSTBASED_SERVICE); 
         
         
    // Get the context for authentication 
    GSSContext context = manager.createContext(serverName, krb5Oid, null, 
       GSSContext.DEFAULT_LIFETIME); 
    context.requestMutualAuth(true); // Request mutual authentication 
    context.requestConf(true); // Request confidentiality 
         
         
    // Do the context establishment loop 
    byte[] token = new byte[0]; 
    while (!context.isEstablished()) { 
      token = context.initSecContext(token, 0, token.length); 
      outStream.writeInt(token.length); 
      outStream.write(token); 
      outStream.flush(); 
         
         
      // Check if we're done 
      if (!context.isEstablished()) { 
        token = new byte[inStream.readInt()]; 
        inStream.readFully(token); 
              } 
            } 
         
         
    // Context established! 
         
         
    // Create MessageProp for use with unwrap (true means request confidentiality) 
    MessageProp prop =  new MessageProp(0, true); 
         
         
    // Create encrypted message and send to server 
    byte[] reply = ...; 
    token = context.wrap(reply, 0, reply.length, prop); 
         
         
    outStream.writeInt(token.length); 
    outStream.write(token); 
    outStream.flush(); 
         
         
    // Read token from server 
    token = new byte[inStream.readInt()]; 
    inStream.readFully(token); 
         
         
    // Unwrap (decrypt) token sent by server 
    byte[] input = context.unwrap(token, 0, token.length, prop); 
            ... 
    context.dispose(); 
    socket.close(); 
    return null; 
          } 
        } 
       
       
       | 
    
  
- サンプルコードをコンパイルします。 
    
% javac GssClient.java
  
- 新規のウィンドウを起動してサーバーを起動します。   
    
% xterm &
% java -Djava.security.auth.login.config=jaas-krb5.conf \
       GssServer
  
- クライアントアプリケーションを実行します。GssClient は、次の 2 つのパラメータを取ります。サービス名、およびそのサービスが実行されているサーバーの名前です。たとえば、サービスがマシン 
j1hol-001 上で実行されている host の場合は、次を入力します。パスワードの入力を求められた場合は、changeit を入力します。    
    
% java -Djava.security.auth.login.config=jaas-krb5.conf \
       GssClient host j1hol-001
      
- クライアントとサーバーのアプリケーションウィンドウに次の出力が表示されます。
 
GssServer の例を実行した場合の出力
  
    
        
      
Authenticated principal:[host/j1hol-001@J1LABS.EXAMPLE.COM] 
Waiting for incoming connections... 
Got connection from client /129.145.128.102 
Context Established! 
Client principal is test@J1LABS.EXAMPLE.COM 
Server principal is host/j1hol-001@J1LABS.EXAMPLE.COM 
Mutual authentication took place! 
Received data "Hello There!" of length 12 
Confidentiality applied:true 
Sending:Hello There!Thu May 06 12:11:15 PDT 2005 
       
       
       | 
    
  
GssClient の例を実行した場合の出力
  
    
        
      
Kerberos password for test:changeit 
Authenticated principal:[test@J1LABS.EXAMPLE.COM] 
Connected to address j1hol-001/129.145.128.102 
Context Established! 
Client principal is test@J1LABS.EXAMPLE.COM 
Server principal is host@j1hol-001 
Mutual authentication took place! 
Sending message:Hello There! 
Will read token of size 93 
Received message:Hello There!Thu May 06 12:11:15 PDT 2005 
       
       
       | 
    
  
まとめ:
この課題では、Java GSS API を使用してお互い安全に認証および通信を行うクライアント/サーバーアプリケーションを記述する方法について学習しました。 
次の手順
- 課題 4 に進み、Java SASL API を使用してお互い安全に認証および通信を行うクライアント/サーバーアプリケーションを記述する方法について学習します。 
 
  
  
- 課題 5 に進み、JSSE を使用してお互い安全に認証および通信を行うクライアント/サーバーアプリケーションを記述する方法について学習します。 
 
  
- 課題 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 では、メッセージごとに機密性をオンまたはオフにすることができます。 
この課題のリソース:
- Java SASL API プログラミングおよび配備ガイド
 
- Java SASL API javadoc
 
- 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 バッファーです。それ以外の場合は、プレーンなアプリケーションデータです。
実行手順:
 
-  次のコードを参照してください。これは 
src/SaslTestServer.java にあります。
このコードは、サービス主体が KDC に対して認証した後に実行するアクションを定義します。 It replaces the MyAction of line 11 of Exercise 1.強調表示されている行に注目してください。サーバーは、サポートする保護の品質を指定し (行 9)、SaslServer のインスタンスを作成して認証を実行します (行 21)。SASL のチャレンジ応答プロトコルは、 while ループ (行 33 ~ 49) の中で実行されています。そこでサーバーは、チャレンジをクライアントに送信しクライアントからの応答を処理します。認証後、認証されたクライアントのアイデンティティーを getAuthorizedID() の呼び出しによって取得できます (行 61)。セキュリティー層がネゴシエーションされた場合、サーバーはクライアントと安全にデータを交換できます (行 66、70)。 
   
SaslTestServer.java のコードリスト
  
    
        
      
static class SaslServerAction implements PrivilegedExceptionAction {  
        ...  
  public Object run() throws Exception { 
    // Create server socket for accepting connections 
    ServerSocket ss = new ServerSocket(localPort); 
         
         
    // Support all quality-of-protection options  
    HashMap<String,Object> props = new HashMap<String,Object>(); 
    props.put(Sasl.QOP, "auth-conf,auth-int,auth"); 
         
         
    while( true ) { 
      // Create application-level connection to handle request 
      Socket socket = ss.accept(); 
      AppConnection conn = new AppConnection(socket); 
         
         
      // Normally, the application protocol will negotiate which 
      // SASL mechanism to use.In this simplified example, we 
      // will always use "GSSAPI" (the name of the mechanism that does Kerberos via GSS-API) 
         
         
      // Create SaslServer to perform authentication 
      SaslServer srv = Sasl.createSaslServer("GSSAPI", service, serviceName, props, cbh); 
         
         
      if (srv == null) { 
        // ... handle error 
              } 
         
         
      // Read the initial response from client 
      byte[] response = conn.receive(AppConnection.AUTH_CMD); 
      AppConnection.AppReply clientMsg; 
      boolean auth = false; 
         
         
      // Perform authentication 
      while (!srv.isComplete()) { 
        try 
          // Generate challenge based on response 
          byte[] challenge = srv.evaluateResponse(response); 
          if (srv.isComplete()) { 
            conn.send(AppConnection.SUCCESS, challenge); 
            auth = true; 
          } else { 
            clientMsg = conn.send(AppConnection.AUTH_IN_PROGRESS, challenge); 
            response = clientMsg.getBytes(); 
                  } 
        } catch (SaslException e) { 
          // Send failure notification to client 
          conn.send(AppConnection.FAILURE, null); 
          break; 
                } 
              } 
         
         
      // Authentication completed! 
         
         
      // Check status of authentication 
      if (srv.isCompleted() && auth) { 
        System.out.println("authorized client is:" + srv.getAuthorizationID()); 
      } else { 
        // Report failure ...  
              } 
         
         
      // Find out whether security layer was negotiated 
      String qop = (String) srv.getNegotiatedProperty(Sasl.QOP); 
      boolean sl = (qop.equals("auth-conf") || qop.equals("auth-int")); 
         
         
      // Read and decrypt message from client 
      byte[] msg = conn.receive(AppConnection.DATA_CMD); 
      byte[] realMsg = (sl ? srv.unwrap(msg, 0, msg.length) :msg); 
              ... 
      // Create and encrypt message to send to client 
      byte[] reply = ...; 
      byte[] realReply = (sl ? srv.wrap(reply, 0, reply.length) :reply); 
      conn.send(AppConnection.SUCCESS, realReply); 
            } 
          } 
        } 
       
       
       | 
    
  
- サンプルコードをコンパイルします。 
  
    
% javac SaslTestServer.java
      
    
- 次のコードを参照してください。これは 
src/SaslTestClient.java にあります。このコードは、クライアント主体が KDC に対して認証した後に実行するアクションを定義します。 It replaces the MyAction of line 11 of Exercise 1.強調表示されている行に注目してください。プログラムは、必要な保護の品質 (この場合は、機密性) を最初に指定し (行 8)、次に SaslClient のインスタンスを作成して認証に使用します (行 11 ~ 12)。次に、機構に初期応答があるかどうかをチェックし、ある場合は、空のバイト配列で evaluateChallenge() を呼び出すことによって応答を取得します (行 20)。次に、応答をサーバーに送信して認証を開始します。SASL のチャレンジ応答プロトコルは、while ループ (行 24 ~ 39) の中で実行されています。そこでクライアントは、サーバーから取得したチャレンジを評価してチャレンジに対応する応答をサーバーに送信します。認証後、クライアントは、ネゴシエーションされたセキュリティー層を使用してサーバーとの通信に進むことができます (行 48、55)。  
SaslTestClient.java のコードリスト
  
    
        
      
static class SaslClientAction implements PrivilegedExceptionAction {  
        ...  
  public Object run() throws Exception { 
    // Create application-level connection 
    AppConnection conn = new AppConnection(serverName, port); 
         
         
    HashMap<String,Object> props = new HashMap<String,Object>(); 
    props.put(Sasl.QOP, "auth-conf"); // Request confidentiality 
         
         
    // Create SaslClient to perform authentication 
    SaslClient clnt = Sasl.createSaslClient( 
      new String[]{"GSSAPI"}, null, service, serverName, props, cbh); 
    if (clnt == null) { 
      // ... handle error 
            } 
         
         
    byte[] challenge; 
         
         
    // Get initial response for authentication 
    byte[] response = clnt.hasInitialResponse() ? clnt.evaluateChallenge(EMPTY) :EMPTY; 
    AppConnection.AppReply reply = conn.send(AppConnection.AUTH_CMD, response); 
         
         
    // Repeat until authentication terminates 
    while (!clnt.isComplete() &&  
      (reply.getStatus() == AppConnection.AUTH_INPROGRESS || 
       reply.getStatus() == AppConnection.SUCCESS)) 
      // Evaluate challenge to generate response 
      challenge = reply.getBytes() 
      response = clnt.evaluateChallenge(challenge) 
         
         
      if (reply.getStatus() == AppConnection.SUCCESS) { 
        if (response != null) { 
          throw new Exception("Protocol error") 
                } 
        break; 
              } 
      // Send response to server and read server's next challenge 
      reply = conn.send(AppConnection.AUTH_CMD, response); 
            } 
    // Authentication completed! 
    // Find out whether security layer was negotiated 
    String qop = (String) srv.getNegotiatedProperty(Sasl.QOP); 
    boolean sl = (qop.equals("auth-conf") || qop.equals("auth-int")); 
         
         
    byte[] msg = ...; 
         
         
    // Create and send encrypted data to server 
    byte[] encrypted = (sl ? clnt.wrap(msg, 0, msg.length) :msg); 
    reply = conn.send(AppConnection.DATA_CMD, encrypted);) { 
         
         
            ... 
         
         
    // Read and decrypt data from server 
    byte[] encryptedReply = reply.getBytes(); 
    byte[] clearReply = (sl ? clnt.unwrap(encryptedReply, 0, encryptedReply.length) 
      : encryptedReply); 
    conn.close(); 
    return null; 
          } 
        } 
       
       
       | 
    
  
- サンプルコードをコンパイルします。 
  
    
% javac SaslTestClient.java
      
    
- 新規のウィンドウを起動してサーバーを起動します。SaslTestServer は、次の 2 つのパラメータを取ります。サービス名、およびそのサービスが実行されているサーバーの名前です。たとえば、サービスがマシン 
j1hol-001 上で実行されている host の場合は、次を入力します。  
    % xterm &
% java 
 -Djava.security.auth.login.config=jaas-krb5.conf \
       SaslTestServer host j1hol-001
    
      
- クライアントアプリケーションを実行します。SaslTestClient は、次の 2 つのパラメータを取ります。サービス名、およびそのサービスが実行されているサーバーの名前です。たとえば、サービスがマシン 
j1hol-001 上で実行されている host の場合は、次を入力します。パスワードの入力を求められた場合は、changeit を入力します。   
    
% java -Djava.security.auth.login.config=jaas-krb5.conf \
       SaslTestClient host j1hol-001
      
- クライアントとサーバーのアプリケーションウィンドウに次の出力が表示されます。
 
SaslTestServer の例を実行した場合の出力 
  
    
        
      
Authenticated principal:[host/j1hol-001@J1LABS.EXAMPLE.COM] 
Waiting for incoming connections... 
Got connection from client /129.145.128.102 
Client authenticated; authorized client is:test@J1LABS.EXAMPLE.COM 
Negotiated QOP:auth-conf 
Received:Hello There! 
Sending:Hello There!Fri May 07 15:32:37 PDT 2005 
Received data "Hello There!" of length 12 
       
       
       | 
    
  
SaslTestClient の例を実行した場合の出力
  
    
        
      
Kerberos password for test:changeit 
Authenticated principal:[test@J1LABS.EXAMPLE.COM] 
Connected to address j1hol-001/129.145.128.102 
Client authenticated. 
Negotiated QOP:auth-conf 
Sending:Hello There! 
Received:Hello There!Fri May 07 15:32:37 PDT 2005 
       
       
       | 
    
  
- 異なる保護の品質を使用してプログラムを試行するには、
SaslTestClient の行 8 を変更します。たとえば、行 8 を次の行で置き換えて、整合性保護を使用します (機密性なし)。 
props.put(Sasl.QOP, "auth-int");
  
まとめ:
この課題では、Java SASL API を使用してお互い安全に認証および通信を行うクライアント/サーバーアプリケーションを記述する方法について学習しました。 
次の手順
- 課題 5 に進み、JSSE を使用してお互い安全に認証および通信を行うクライアント/サーバーアプリケーションを記述する方法について学習します。 
 
- 課題 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 をいつ使用するか」に示されています。 
この課題のリソース:
- Java Secure Socket Extension (JSSE) リファレンスガイド
 
- JSSE javadoc:javax.net および javax.net.ssl
 
  
- The SSL Protocol version 3.0
 
- The TLS Protocol Version 1.0 (RFC 2246)
 
- Addition of Kerberos Cipher Suites to Transport Layer Security TLS (RFC 2712) 
 
-  Java GSS-API および JSSE をいつ使用するか 
 
この課題の概要:
この課題では、JSSE および Kerberos 暗号化方式を使用して安全に通信する方法を示すクライアントサーバーアプリケーションを扱います。クライアント部分とサーバー部分は、課題 1 を使用して最初に Kerberos への認証を行います。これにより、資格が被認証者に格納されます。アプリケーションは、被認証者を使用して Subject.doAs の内部で、(Kerberos 暗号化方式を使用して) JSSE 操作を実行するアクションを実行します。Kerberos 暗号化方式の実装は doAs の内部で実行されるため、Kerberos 資格を被認証者から取得し、それらを使用してピアとの認証およびメッセージの安全な交換を行います。この例では、改行で終了されるメッセージ (ネゴシエーションが行われた暗号群を使用して暗号化され、整合性が保護される) を、クライアントとサーバー間で送受信します。
標準 (RFC 2712) に従って、Kerberos が有効なすべての TLS アプリケーションは、同じサービス名、つまり「host」を使用します。このため、この課題では、Kerberos サービス名を明示的に指定する必要はありません。
実行手順:
-  次のコードを参照してください。これは、
src/JsseServer.java にあります。
このコードは、サービス主体が KDC に対して認証した後に実行するアクションを定義します。 It replaces the MyAction of line 11 of Exercise 1.強調表示されている行に注目してください。サーバーは最初に、SSLServerSocket を作成します (行 5 ~ 8)。これは、SSLServerSocket が必要に応じて自動的な認証、暗号化、および復号化を提供する点を除いて、プレーンな ServerSocket を作成するアプリケーションと似ています。次に、サーバーは使用する暗号群を設定します (行 11 ~ 12)。次に、サーバーはループで実行され、SSL クライアントからの接続を受け入れ (行 17)、SSL ソケットから読み書きします (行 23、28)。サーバーは、getLocalPrincipal() および getPeerPrincipal() メソッドを呼び出すことによって (行 32 ~ 33)、ソケットの所有者のアイデンティティーを調べることができます。 
   
JsseServer.java のコードリスト
  
    
        
      
static class JsseServerAction implements PrivilegedExceptionAction {  
        ...  
  public Object run() throws Exception { 
    // Create TLS socket for accepting connections 
    SSLServerSocketFactory sslssf = 
      (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); 
    SSLServerSocket sslServerSocket = 
      (SSLServerSocket) sslssf.createServerSocket(localPort); 
         
         
    // Enable only a Kerberos cipher suite   
    String enabledSuites[] = { "TLS_KRB5_WITH_3DES_EDE_CBC_SHA" }; 
    sslServerSocket.setEnabledCipherSuites(enabledSuites); 
    // Should handle exception if enabledSuites is not supported 
         
         
    while( true ) { 
      // Create socket to handle request 
      SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); 
      BufferedReader in = new BufferedReader(new InputStreamReader( 
        sslSocket.getInputStream())); 
      BufferedWriter out = new BufferedWriter(new OutputStreamWriter( 
        sslSocket.getOutputStream())); 
         
         
      String inStr = in.readLine(); 
      // ... use inStr 
         
         
      // Compose and send reply 
      String outStr = inStr + " " + new Date().toString() + "\n"; 
      out.write(outStr); 
      out.flush(); 
         
         
      // Get names of principal at both ends of secure connection 
      Principal self = sslSocket.getSession().getLocalPrincipal(); 
      Principal peer = sslSocket.getSession().getPeerPrincipal(); 
         
         
      sslSocket.close(); 
            } 
          } 
        } 
       
       
       | 
    
  
- サンプルコードをコンパイルします。 
    
% javac JsseServer.java
      
- 次のコードを参照してください。これは 
src/JsseClient.java にあります。このコードは、クライアント主体が KDC に対して認証した後に実行するアクションを定義します。このコードによって、課題 1 の行 11 の MyAction が置き換えられます。強調表示されている行に注目してください。クライアントは最初に、SSLSocket を作成します。次に、クライアントは使用する暗号群を設定します (行 11 ~ 12)。次に、クライアントは、SSLSocket を使用してソケットの入力/出力ストリームを読み書きすることによって、サーバーとメッセージを交換します。クライアントは、getLocalPrincipal() および getPeerPrincipal() メソッドを呼び出すことによって (行 26 ~ 27)、ソケットの所有者のアイデンティティーを調べることができます。  
JsseClient.java のコードリスト
  
    
        
      
static class JsseClientAction implements PrivilegedExceptionAction {  
        ...  
  public Object run() throws Exception { 
    // Create SSL connection 
    SSLSocketFactory sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault(); 
    SSLSocket sslSocket = (SSLSocket) sslsf.createSocket(server, port); 
         
         
    // Enable only a Kerberos cipher suite   
    String enabledSuites[] = { "TLS_KRB5_WITH_3DES_EDE_CBC_SHA" }; 
    sslSocket.setEnabledCipherSuites(enabledSuites); 
    // Should handle exception if enabledSuites is not supported 
         
         
    BufferedReader in = new BufferedReader(new InputStreamReader( 
      sslSocket.getInputStream())); 
    BufferedWriter out = new BufferedWriter(new OutputStreamWriter( 
      sslSocket.getOutputStream())); 
         
         
    String outStr = ...; 
    out.write(outStr); 
    out.flush(); 
         
         
    String inStr = in.readLine(); 
    // ... use inStr 
         
         
    // Get names of principal at both ends of secure connection 
    Principal self = sslSocket.getSession().getLocalPrincipal(); 
    Principal peer = sslSocket.getSession().getPeerPrincipal(); 
         
         
    sslSocket.close(); 
    return null; 
          } 
        } 
       
       
       | 
    
  
- サンプルコードをコンパイルします。 
    
% javac JsseClient.java
      
    
- 新規のウィンドウを起動してサーバーを起動します。JsseServer は、パラメータを 1 つ取ります。JSSE サービスが実行されているサーバーの名前です。たとえば、マシン 
j1hol-001 上で実行されている場合は、次のように入力します。  
    % xterm &
% java 
 -Djava.security.auth.login.config=jaas-krb5.conf \
       JsseServer j1hol-001
    
      
    
    
- クライアントアプリケーションを実行します。JsseClient は、パラメータを 1 つ取ります。JSSE サービスが実行されているサーバーの名前です。たとえば、サービスがマシン 
j1hol-001 上で実行されている場合は、次のように入力します。パスワードの入力を求められた場合は、changeit を入力します。   
    
% java -Djava.security.auth.login.config=jaas-krb5.conf \
       JsseClient j1hol-001
      
- クライアントとサーバーのアプリケーションウィンドウに次の出力が表示されます。
 
JsseServer の例を実行した場合の出力
  
    
        
      
Authenticated principal:[host/j1hol-001@J1LABS.EXAMPLE.COM] 
Waiting for incoming connections... 
Got connection from client /129.145.128.102 
Received:Hello There! 
Sending:Hello There!Fri May 07 15:32:37 PDT 2005 
Cipher suite in use:TLS_KRB5_WITH_3DES_EDE_CBC_SHA 
I am:host/j1hol-001@J1LABS.EXAMPLE.COM 
Client is:test@J1LABS.EXAMPLE.COM 
       
       
       | 
    
  
JsseClient の例を実行した場合の出力
  
    
        
      
Kerberos password for test:changeit 
Authenticated principal:[test@J1LABS.EXAMPLE.COM] 
Sending:Hello There! 
Received:Hello There!Fri May 07 15:32:37 PDT 2005 
Cipher suite in use:TLS_KRB5_WITH_3DES_EDE_CBC_SHA 
I am:test@J1LABS.EXAMPLE.COM 
Server is:host/j1hol-001@J1LABS.EXAMPLE.COM 
       
       
       | 
    
  
まとめ:
この課題では、基盤となる認証システムとして Kerberos を使用し、JSSE を使用してお互い安全に認証および通信を行うクライアント/サーバーアプリケーションを記述する方法について学習しました。 
次の手順
- 課題 6 に進み、課題 3、4、および 5 のサンプルプログラムを構成して Kerberos 環境でシングルサインオンを行う方法について学習します。