このチュートリアルでは、相互に通信するアプリケーション間でセキュアなメッセージ交換を実行するためのJava GSS-APIの使用方法を示す2つのサンプル・アプリケーションを紹介します。ここでは、クライアント・アプリケーションとサーバー・アプリケーションを例として使用しています。
Java GSS-APIは、「セキュリティ・メカニズム」と呼ばれるメカニズムを使用して、これらのサービスを提供します。GSS-API実装には、Kerberos V5メカニズムのサポートと、他のベンダー固有の任意の選択肢が含まれます。このチュートリアルでは、Kerberos V5メカニズムを使用しています。
クライアントとサーバー間の認証を実行し、セキュアな通信用の暗号化鍵を確立するために、GSS-APIメカニズムは、接続の両側のローカル・エンティティ用の特定のクレデンシャルにアクセスする必要があります。このチュートリアルでは、クライアント側で使用するクレデンシャルはKerberosチケットで構成され、サーバー側のクレデンシャルは長期Kerberos秘密鍵で構成されます。Kerberosチケットには、ホスト・アドレスをオプションで組み込むことができ、IPv4とIPv6の両方のホスト・アドレスがサポートされています。Java GSS-APIは、メカニズムが、これらのクレデンシャルを、スレッドのアクセス制御コンテキストに関連付けられたサブジェクトから取得することを要求します。
通常、この種のクレデンシャルを持つサブジェクトを生成するため、クライアント・アプリケーションおよびサーバー・アプリケーションは、Kerberosモジュールを使用してJAAS認証を最初に実行します。実行方法の詳細は、「JAAS認証」チュートリアルを参照してください。認証されたサブジェクトをスレッドのアクセス制御コンテキストに関連付ける方法については、「JAAS承認」を参照してください。これらの操作を自動的に実行するユーティリティも用意されています。Loginユーティリティの使用方法については、「JAAS Loginユーティリティの使用」チュートリアルを参照してください。
このチュートリアルでは、クライアントおよびサーバーでJAAS認証を実行せず、Loginユーティリティを使用しません。その代わり、システム・プロパティjavax.security.auth.useSubjectCredsOnly
をfalse
に設定する必要があります。この設定により、JAASによって設定された既存のサブジェクトから必要なクレデンシャルを取得する際、GSSメカニズムを必要とする制限が緩和されます。「useSubjectCredsOnlyシステム・プロパティ」を参照してください。
注: これは簡略化された入門用のチュートリアルです。たとえば、ポリシー・ファイルは含まれておらず、サンプル・コードの実行にセキュリティ・マネージャを使用していません。実際には、Java GSS-APIを使用するコードは、セキュリティ・マネージャを使用して実行すべきであるため、必要なアクセス権が明示的に付与されていないかぎり、セキュリティ関連の操作は許可されません。
別のチュートリアル、「JAAS LoginユーティリティおよびJava GSS-APIを使用したセキュアなメッセージ交換」の内容は、このユーティリティと類似していますが、Loginユーティリティ、ポリシー・ファイル、およびより複雑なログイン構成ファイルを利用する点が異なります(ログイン構成ファイルは、JAAS認証の実行時には常に必要なファイルで、使用する認証モジュールを指定する)。
この一連のすべてのチュートリアルでは、認証およびアプリケーションのセキュアな通信をサポートする基盤の技術として、Kerberos V5を使用しています。「Kerberos要件」を参照してください。
チュートリアルのコードを最初に実行してみる場合、「SampleClientおよびSampleServerプログラムの実行」を先に読んでから、その他のセクションに戻り、学習を続けてください。
このチュートリアルで使用するアプリケーションの名前は、SampleClientおよびSampleServerです。
次に、SampleClientおよびSampleServerアプリケーションの実行方法のサマリーを示します。
実際のコードおよび詳細は、後述のセクションで示します。
SampleClientおよびSampleServerプログラムの全体のコードは、main
メソッド内に配置されており、次の区分にさらに分割できます。
注: これらのプログラムが使用するJava GSS-APIクラス(GSSManager、GSSContext、GSSName、GSSCredential、MessageProp、およびOid)は、org.ietf.jgss
パッケージ内にあります。
クライアントとサーバーのmain
メソッドが最初に行うことは、コマンド行引数の読取りです。
SampleClientは、次の3つの引数を受け付けます。
次に、コマンド行引数を読み取るコードを示します。
if (args.length < 3) { System.out.println("Usage: java <options> Login SampleClient " + " <servicePrincipal> <hostName> <port>"); System.exit(-1); } String server = args[0]; String hostName = args[1]; int port = Integer.parseInt(args[2]);
SampleServerが受け付ける引数は、次の1つだけです。
次に、コマンド行引数を読み取るコードを示します。
if (args.length != 1) { System.out.println( "Usage: java <options> Login SampleServer <localPort>"); System.exit(-1); } int localPort = Integer.parseInt(args[0]);
Java GSS-APIは、トークン(不透明なバイト・データ)を作成および解釈するメソッドを提供します。トークンには2つのピア間でセキュアに交換されるメッセージが含まれていますが、実際にトークンを転送するメソッドはピアによって異なります。SampleClientおよびSampleServerアプリケーションの場合、クライアントとサーバー間のソケット接続を確立し、ソケット入力および出力ストリームを使ってデータを交換します。
SampleClientには、引数としてSampleServerを実行中のホスト・マシン名と、SampleServerが接続を待機するポート番号が渡されました。これで、SampleClientからSampleServerへのソケット接続確立に必要なすべてが整いました。次のコードを使用して、接続を設定し、将来のデータ交換に備えてDataInputStreamおよびDataOutputStreamを初期化します。
Socket socket = new Socket(hostName, port); DataInputStream inStream = new DataInputStream(socket.getInputStream()); DataOutputStream outStream = new DataOutputStream(socket.getOutputStream()); System.out.println("Connected to server " + socket.getInetAddress());
SampleServerアプリケーションには、引数として、クライアントからの接続待機に使用するポート番号が渡されます。このアプリケーションは、指定されたポート上で待機するServerSocketを作成します。
ServerSocket ss = new ServerSocket(localPort);
次に、ServerSocketはクライアントから接続を待機および受け付け、クライアントとの将来のデータ交換に備えてDataInputStreamおよびDataOutputStreamを初期化します。
Socket socket = ss.accept(); DataInputStream inStream = new DataInputStream(socket.getInputStream()); DataOutputStream outStream = new DataOutputStream(socket.getOutputStream()); System.out.println("Got connection from client " + socket.getInetAddress());
accept
メソッドは、クライアント(ここではSampleClient)がSampleServerのホストおよびポート上で接続を要求するまで待機します。SampleClientはこの接続を介して操作を実行します。
Socket socket = new Socket(hostName, port);
接続の要求および確立が実行されると、accept
メソッドは新規ポートにバインドされた新規Socketオブジェクトを返します。サーバーは、この新規ソケット経由でクライアントと通信しつつ、元のポートにバインドされたServerSocketに対するほかのクライアントからの接続要求を待機できます。このため、一般に、サーバー・プログラムは複数の接続要求を処理可能なループを保持します。
SampleServerの基本的なループ構造を、次に示します。
while (true) { Socket socket = ss.accept(); <Establish input and output streams for the connection>; <Establish a context with the client>; <Exchange messages with the client>; <Clean up>; }
クライアント接続は、元のポートでキューに入れられます。このため、SampleServerの使用するプログラム構造では、接続を行う最初のクライアントとのやり取りが完了してからでないと、次の接続を受け付けることができません。実際のところ、サーバーは次のように、クライアントごとに1つのスレッドを使用することで、複数のクライアントに同時に対応できます。
while (true) { <accept a connection>; <create a thread to handle the client>; }
2つのアプリケーションがJava GSS-APIを利用してセキュアにメッセージを交換するためには、クレデンシャルを使用して、事前にジョイント・セキュリティ・コンテキストを確立しておく必要があります。(注: SampleClientの場合、クレデンシャルは、SampleClientが自動的に実行され、Loginユーティリティによってユーザーが認証された時点で確立されます。SampleServerの場合も同様です。)セキュリティ・コンテキストは、共有状態の情報(暗号化鍵などを含む)をカプセル化します。暗号化が要求された場合、こうした鍵を使用して、交換するメッセージを暗号化できます。
セキュリティ・コンテキスト確立の一部として、コンテキスト・イニシエータ(ここではSampleClient)がアクセプタ(SampleServer)に認証され、それに対し、アクセプタがイニシエータに認証されることが必要になる場合があります。これを、「相互認証」が行われると言います。
両方のアプリケーションが、GSSContextオブジェクトを作成および使用して、セキュリティ・コンテキストを構成する共有情報を確立および保守します。
コンテキスト・オブジェクトをインスタンス化する方法は、コンテキスト・イニシエータとコンテキスト・アクセプタで異なります。イニシエータは、GSSContextをインスタンス化した後で、目的のセキュリティ・コンテキストの特性を決定する様々なコンテキスト・オプション(相互認証を行うかどうかの指定など)を設定できます。目的の特性をすべて設定したら、イニシエータはinitSecContext
メソッドを呼び出します。このメソッドは、アクセプタのacceptSecContext
メソッドが必要とするトークンを生成します。
Java GSS-APIメソッドは、アプリケーション間で交換されるトークンを準備するために存在します。ただし、実際にアプリケーション間でトークンを転送するのは、アプリケーションの役割です。このため、イニシエータは、initSecContext
の呼出しによりトークンを受け取ると、それをアクセプタに送信します。アクセプタは、acceptSecContext
を呼び出してトークンを渡します。それに対し、acceptSecContext
メソッドがトークンを返すことができます。その場合、アクセプタは、そのトークンをイニシエータに送信し、イニシエータはinitSecContext
を再度呼び出してこのトークンを渡すようにすべきです。initSecContext
またはacceptSecContext
がトークンを返すたびに、メソッドを呼び出したアプリケーションはトークンをピアに送信し、ピアはトークンを適切なメソッド(acceptSecContext
またはinitSecContext
)に渡すようにしてください。この処理は、コンテキストが完全に確立される(コンテキストのisEstablished
メソッドがtrue
を返す)まで継続して行われます。
サンプル・アプリケーション用のコンテキスト確立コードについては、次で説明します。
このクライアント/サーバーのシナリオでは、SampleClientはコンテキスト・イニシエータです。次に、セキュリティ・コンテキストを確立するための基本的な手順を示します。次のとおりです。
initSecContext
を呼び出すたびに、返されたトークンをすべてSampleServerに送信し、SampleServerからトークンを受け取ります(トークンが存在する場合)。GSSContextは、GSSManagerのインスタンス化、およびいずれかのcreateContext
メソッドの呼出しにより作成されます。GSSManagerクラスは、ほかの重要なGSS APIクラスのファクトリとして機能します。このクラスは、GSSContext、GSSCredential、およびGSSNameインタフェースを実装するクラスのインスタンスを作成します。
SampleClientは、GSSManagerのstaticメソッドgetInstance
を呼び出すことにより、デフォルトのGSSManagerサブクラスのインスタンスを取得します。
GSSManager manager = GSSManager.getInstance();
デフォルトGSSManagerサブクラスのcreate*
メソッド(createContext
など)が返すクラスの実装は、基盤となるテクノロジとしてKerberosをサポートします。
イニシエータ側でコンテキストを作成するGSSManagerファクトリ・メソッドは、次のシグニチャを保持します。
GSSContext createContext(GSSName peer, Oid mech, GSSCredential myCred, int lifetime);
次に、この引数について説明します。そのあとで、createContext
への完全な呼出しを示します。
GSSName peer
引数クライアント/サーバー・パラダイムにおけるピアは、サーバーです。peer
引数には、サーバーを表すサービス・プリンシパルのGSSNameを指定する必要があります。(「Kerberosユーザー名およびサービス・プリンシパル名」を参照)。サービス・プリンシパル名を表す文字列は、SampleClientの最初の引数として渡されます。SampleClientは引数をserver
という名前のローカルのString変数に格納します。GSSNameのインスタンス化には、GSSManager manager
が(そのcreateName
メソッドのいずれかを呼び出すことにより)使用されます。SampleClientは、次の署名を使用して、createName
メソッドを呼び出します。
GSSName createName(String nameStr, Oid nameType);
SampleClientは、nameStr
引数としてserver
Stringを渡します。
2番目の引数は、Oidです。Oidは、汎用オブジェクト識別子(Universal Object Identifier)を表します。Oidは、メカニズムと名前の型を識別するためにGSS-APIフレームワーク内で用いられる、グローバルに解釈できる階層的な識別子です。Oidの構造およびエンコードは、ISOIEC-8824およびISOIEC-8825標準で定義されています。createName
メソッドに渡されるOidは、厳密にはメカニズムOidではなく、名前型Oidです。
GSS-APIでは、文字列名は、しばしばメカニズムに依存しない形式からメカニズム固有の形式にマッピングされます。通常、Oidでは、メカニズムがマッピング方法を認識できるよう、文字列の名前形式が指定されます。null
のOidは、名前が、メカニズムの使用するネイティブの形式になっていることを示します。これが当てはまるのは、適切なKerberos Version 5名の形式であるserver
Stringの場合です。このため、SampleClientは、Oidとしてnull
を渡します。次に、その呼出しを示します。
GSSName serverName = manager.createName(server, null);
Oid mech
引数GSSManager createContext
メソッドの2番目の引数はOidです。これは、コンテキスト確立時のクライアントとサーバー間の認証、およびそのあとのクライアントとサーバー間のセキュアな通信に使用されるメカニズムを表します。
このチュートリアルでは、セキュリティ・メカニズムとしてKerberos V5を使用しています。Kerberos V5メカニズムのOidは、RFC 1964で、「1.2.840.113554.1.2.2」と定義されているので、このOidを作成します。
Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
SampleClientは、createContext
の2番目の引数としてkrb5Oid
を渡します。
GSSCredential myCred
引数GSSManager createContext
メソッドの3番目の引数は、呼出し側のクレデンシャルを表すGSSCredentialです。SampleClientの場合と同様、この引数にnull
を渡す場合、デフォルトのクレデンシャルが使用されます。
int lifetime
引数GSSManager createContext
メソッドの最後の引数はint
で、作成するコンテキストのライフ・タイムを秒で指定します。SampleClientは、GSSContext.DEFAULT_LIFETIME
を渡して、デフォルトのライフ・タイムを要求します。
ここまでで、必要な引数をすべて説明しました。ここでは、GSSContextを作成するSampleClientの呼出しを示します。
GSSContext context = manager.createContext(serverName, krb5Oid, null, GSSContext.DEFAULT_LIFETIME);
コンテキストのインスタンス化が完了したら、コンセプト・アクセプタを使用してコンテキストを実際に確立する前に、コンテキスト・イニシエータで、目的のセキュリティ・コンテキストの特性を決定する様々なオプションを設定できます。それらの各オプションは、インスタンス化されたコンテキスト上でrequest
メソッドを呼び出すことで設定されます。大半のrequest
メソッドは、その機能を要求するかどうかを示すboolean
引数を取ります。要求は、常に受け入れられるわけではないため、コンテキストの確立後に、いずれかのget
メソッドを呼び出して、それを確認できます。
SampleClientは、次のオプションを要求します。
wrap
という名前のコンテキスト・メソッドの暗号化を有効にすることを意味します。暗号が実際に使用されるのは、wrap
メソッドに渡されるMessagePropオブジェクトにより機密性が要求される場合だけです。wrap
およびgetMIC
メソッドの整合性を要求します。整合性が要求されると、これらのメソッドの呼出し時に、メッセージ整合コード(MIC)として知られる暗号化タグが生成されます。getMIC
の呼出しにより返されるトークン内に、生成されたMICが含まれます。wrap
が呼び出されると、MICがメッセージ(元のメッセージまたはメッセージ暗号化の結果、機密性が適用されたかどうかにより異なる)とともに1つのトークンの一部としてパッケージ化されます。次に、メッセージに対するMICを検証して、メッセージが途中で変更されていないことを確認できます。GSSException context
に対してこれらの要求を生成するSampleClientコードを、次に示します。
context.requestMutualAuth(true); // Mutual authentication context.requestConf(true); // Will use encryption later context.requestInteg(true); // Will use integrity later
注: デフォルトのGSSManager実装およびKerberosメカニズムを使用する場合、これらの要求は常に許可されます。
SampleClientは、GSSContextのインスタンス化および使用するコンテキスト・オプションの指定後に、SampleServerとのセキュリティ・コンテキストを実際に確立できます。このために、SampleClientはループ構造を保持します。各ループにより、次が反復実行されます。
initSecContext
メソッドの呼出し。最初の呼出しの場合、メソッドにはnull
のトークンが渡されます。それ以外の場合、SampleServerからSampleClientに直前に送信されたトークン(SampleServerによるacceptSecContext
の呼出しで生成されるトークン)が渡されます。initSecContext
により返されたトークン(存在する場合)のSampleServerへの送信。initSecContext
への最初の呼出しでは、常にトークンが生成されます。最後の呼出しでは、トークンが返されない場合があります。initSecContext
から返される、またはSampleServerから受け取るトークンは、バイト配列に格納されます。トークンは、SampleClientおよびSampleServer間での送信時に不透明なデータとして処理され、Java GSS-APIメソッドにより解釈されます。
initSecContext
引数は、トークン、トークン開始位置の配列内の開始オフセット、およびトークンの長さを含むバイト配列です。最初の呼出しでは、SampleClientは、SampleServerからトークンをまだ受け取っていないため、nullのトークンを渡します。
SampleServerとトークンを交換する際、SampleClientは、SampleServerへのソケット接続用入力および出力ストリームを使用して設定した、DataInputStream inStream
およびDataOutputStream outStream
を使用します。トークンを記述する場合、常に、トークンのバイト数を最初に記述してから、トークン自体を記述してください。その理由は、「SampleClientとSampleServerのメッセージ交換」セクションの導入部で説明します。
次に、SampleClientのコンテキスト確立ループ、およびクライアントおよびサーバーの実体、相互認証を実際に行うかどうかなどの情報を表示するコードを示します。
byte[] token = new byte[0]; while (!context.isEstablished()) { // token is ignored on the first call token = context.initSecContext(token, 0, token.length); // Send a token to the server if one was generated by // initSecContext if (token != null) { System.out.println("Will send token of size " + token.length + " from initSecContext."); outStream.writeInt(token.length); outStream.write(token); outStream.flush(); } // If the client is done with context establishment // then there will be no more tokens to read in this loop if (!context.isEstablished()) { token = new byte[inStream.readInt()]; System.out.println("Will read input token of size " + token.length + " for processing by initSecContext"); inStream.readFully(token); } } System.out.println("Context Established! "); System.out.println("Client is " + context.getSrcName()); System.out.println("Server is " + context.getTargName()); if (context.getMutualAuthState()) System.out.println("Mutual authentication took place!");
このクライアント/サーバーのシナリオでは、SampleServerはコンテキスト・アクセプタです。次に、セキュリティ・コンテキストを確立するための基本的な手順を示します。次のとおりです。
acceptSecContext
を呼び出してトークンを渡し、返されたトークンをすべてSampleClientに送信します。「SampleClient GSSContextのインスタンス化」で説明したように、GSSContextは、GSSManagerをインスタンス化して、createContext
メソッドのいずれかを呼び出すことで作成されます。
SampleClientと同様、SampleServerは、GSSManagerのstaticメソッドgetInstance
を呼び出すことにより、デフォルトのGSSManagerサブクラスのインスタンスを取得します。
GSSManager manager = GSSManager.getInstance();
アクセプタ側でコンテキストを作成するGSSManagerファクトリ・メソッドは、次のシグニチャを保持します。
GSSContext createContext(GSSCredential myCred);
SampleServerの場合と同様、GSSCredential引数にnull
を渡す場合、デフォルトのクレデンシャルが使用されます。コンテキストは、次の方法でインスタンス化されます。
GSSContext context = manager.createContext((GSSCredential)null);
SampleServerは、GSSContextをインスタンス化したあとで、SampleClientとのセキュリティ・コンテキストを確立できます。これを実行するため、SampleServerはコンテキストが確立されるまで反復するループ構造を保持します。各ループにより、次の操作が反復実行されます。
initSecContext
呼出しの結果です。acceptSecContext
メソッドの呼び出し、および直前に受け取ったトークンのメソッドへの引渡し。acceptSecContext
がトークンを返す場合、SampleServerはこのトークンをSampleClientに送信し、コンテキストがまだ確立されていない場合、次のループ処理を開始します。acceptSecContext
から返される、またはSampleClientから受け取るトークンは、バイト配列に格納されます。
acceptSecContext
引数は、トークン、トークン開始位置の配列内の開始オフセット、およびトークンの長さを含むバイト配列です。
SampleClientとトークンを交換する際、SampleServerは、SampleClientへのソケット接続用入力および出力ストリームを使用して設定した、DataInputStream inStream
およびDataOutputStream outStream
を使用します。
次に、SampleServerのコンテキスト確立ループを示します。
byte[] token = null; while (!context.isEstablished()) { token = new byte[inStream.readInt()]; System.out.println("Will read input token of size " + token.length + " for processing by acceptSecContext"); inStream.readFully(token); token = context.acceptSecContext(token, 0, token.length); // Send a token to the peer if one was generated by // acceptSecContext if (token != null) { System.out.println("Will send token of size " + token.length + " from acceptSecContext."); outStream.writeInt(token.length); outStream.write(token); outStream.flush(); } } System.out.print("Context Established! "); System.out.println("Client is " + context.getSrcName()); System.out.println("Server is " + context.getTargName()); if (context.getMutualAuthState()) System.out.println("Mutual authentication took place!");
SampleClientとSampleServer間のセキュリティ・コンテキストが確立されると、そのコンテキストを使用してメッセージをセキュアに交換できます。
メッセージのセキュアな交換のためにメッセージを準備するメソッドには、wrap
とgetMIC
の2種類があります。実際には、2つのwrap
メソッド(および2つのgetMIC
メソッド)が存在します。2つのメソッドの相違点は、入力メッセージの位置(バイト配列または入力ストリーム)および出力先(バイト配列の戻り値または出力ストリーム)です。
メッセージ交換用のこれらのメソッド、および対応するメソッド(生成されるトークンのピアが解釈を行うための)について、次で説明します。
wrap
メソッドは、メッセージ交換用のプライマリ・メソッドです。
SampleClientにより呼び出されるwrap
メソッドのシグニチャは、次のようになります。
byte[] wrap (byte[] inBuf, int offset, interface len, MessageProp msgProp)
wrap
に、メッセージ(inBuf
内)、inBuf
内でのメッセージ開始位置のオフセット(offset
)、およびメッセージの長さ(len
)を渡します。使用するQOP (Quality-of-Protection)、および機密性(暗号化)が必要かどうかを指定する際に使用するMessagePropも渡します。QOP値により、使用する暗号化整合および暗号化(必要な場合)アルゴリズムが決定されます。基盤となるメカニズムのプロバイダにより、さまざまなQOP値に対応するアルゴリズムが指定されます。たとえば、Kerberos V5に対応する値は、RFC 1964のセクション4.2で定義されています。デフォルトのQOPを要求する場合は、通常QOP値に0を指定します。
wrap
メソッドは、メッセージを含むトークンおよびその暗号化メッセージ整合コード(MIC)を返します。MessagePropにより機密性が指定されている場合、トークンに格納されたメッセージは暗号化されます。返されるトークンは、不透明なデータとして扱われるため、その形式について知る必要はありません。返されるトークンをピア・アプリケーションに送信すると、unwrap
メソッドが呼び出されてトークンの「ラップが解除」され、元のメッセージの取得および整合性の検証が行われます。
指定されたメッセージの暗号化メッセージ整合コード(MIC)を含むトークンの取得だけを行う場合、getMIC
を呼び出します。たとえば、ピアが同じデータを保持していることを確認する場合、データ自体を相互に転送しなくても、データのMICを転送するだけで目的を達成できます。
SampleServerにより呼び出されるgetMIC
メソッドのシグニチャは、次のようになります。
byte[] getMIC (byte[] inMsg, int offset, int len, MessageProp msgProp)
getMIC
に、メッセージ(inMsg
内)、inMsg
内でのメッセージ開始位置のオフセット(offset
)、およびメッセージの長さ(len
)を渡します。使用するQOP (Quality-of-Protection)の指定に使うMessagePropも渡します。デフォルトのQOPを要求する場合は、通常QOP値に0を指定します。
getMIC
により作成されたトークンおよびMICの計算に使用するメッセージ(またはMICを計算する予定のメッセージ)を保持する場合、verifyMIC
メソッドを呼び出して、メッセージのMICを検証できます。検証が成功する(GSSExceptionがスローされない)場合、メッセージが、MIC計算時のメッセージと正確に一致することが保証されます。通常、アプリケーションからメッセージを受け取るピアは、MICを検証して、途中でメッセージが変更または破損していないかどうかを確認できるように、MICも期待します。注: メッセージに加え、MICも必要であることが前もってわかっている場合は、wrap
およびunwrap
メソッドを使用する方が便利です。ただし、メッセージとMICを別個に受け取る状況も考えられます。
前述のgetMIC
に対応するverifyMIC
のシグニチャを、次に示します。
void verifyMIC (byte[] inToken, int tokOffset, int tokLen, byte[] inMsg, int msgOffset, int msgLen, MessageProp msgProp);
これにより、inMsg
(長さがmsgLen
で、オフセットmsgOffset
から始まる)内のメッセージに対応する、inToken
(長さがtokLen
で、オフセットtokOffset
から始まる)内のMICの検証が実行されます。基盤となるメカニズムは、MessagePropを使用して呼出し側に情報(メッセージに適用された保護の強さを示すQOPなど)を返します。
SampleClientとSampleServerとの間のメッセージ交換の概要を次に示します。そのあと、コードの詳細を示します。
これらのステップは、GSS-APIクライアントおよびサーバーの検証に使用される「標準的な」ステップです。GSS-APIライブラリの異なる実装間の相互運用性をチェックする、広く普及したテスト・プログラムとなったGSS-APIクライアントおよびGSS-APIサーバーは、MITのグループが記述しました。これらのGSS-APIサンプル・アプリケーションは、MITからダウンロード可能なKerberosディストリビューションの一部です(http://web.mit.edu/kerberos)。MITから入手可能なこのクライアントおよびサーバーが準拠するプロトコルでは、コンテキストの確立後にクライアントが送信したメッセージに、MICが追加されて返されます。GSS-APIライブラリを実装する場合、そのライブラリ実装を使用するクライアントまたはサーバーを、別のGSS-APIライブラリ実装を使用する対応するピア・サーバーまたはクライアントに対して実行して、テストを行うのが一般的な方法です。両方のライブラリ実装が標準に準拠する場合、2つのピアの通信は成功します。
使用するクライアントまたはサーバーを、C言語で記述されたクライアントまたはサーバー(MIT提供のクライアントやサーバーなど)に対してテストする場合、トークンの交換方法を考慮する必要があります。C言語によるGSS-APIの実装には、ストリーム・ベースのメソッドが含まれません。ピアにストリーム・ベースのメソッドが存在しない状況でトークンを記述する場合、最初にバイト数を記述してから、トークンを記述する必要があります。同様に、トークンを読み取る際、バイト数を読み取ってからトークンを読み取る必要があります。SampleClientおよびSampleServerは、実際にこの処理を実行します。
SampleClientおよびSampleServerが行うメッセージ交換のサマリーを、次に示します。
wrap
を呼び出して、メッセージのMICを暗号化および計算します。wrap
から返されたトークンをSampleServerに送信します。unwrap
を呼び出して元のメッセージを取得し、整合性を検証します。getMIC
を呼び出して、復号化したメッセージに対するMICを計算します。getMIC
により返されたトークン(MICを含む)をSampleClientに送信します。verifyMIC
を呼び出して、SampleServerにより送信されたMICが元のメッセージの有効なMICかどうかを検証します。メッセージの暗号化、MICの計算、および結果のSampleServerへの送信を実行するSampleClientコードを、次に示します。
byte[] messageBytes = "Hello There!\0".getBytes(); /* * The first MessageProp argument is 0 to request * the default Quality-of-Protection. * The second argument is true to request * privacy (encryption of the message). */ MessageProp prop = new MessageProp(0, true); /* * Encrypt the data and send it across. Integrity protection * is always applied, irrespective of encryption. */ token = context.wrap(messageBytes, 0, messageBytes.length, prop); System.out.println("Will send wrap token of size " + token.length); outStream.writeInt(token.length); outStream.write(token); outStream.flush();
次のSampleServerコードは、SampleClientにより送信されたラップ済みのトークンを読み取り、「ラップを解除」して元のメッセージを取得し、整合性を検証します。この場合、メッセージが暗号化されているため、ラップの解除には復号化も含まれます。
注: ここでは、整合性チェックは成功するものと考えることができます。ただし、一般に、整合性チェックの失敗は、メッセージが途中で変更されたことが原因です。unwrap
メソッドは、整合性チェックの失敗に遭遇すると、GSSExceptionをメジャー・エラー・コードGSSException.BAD_MICとともにスローします。
/* * Create a MessageProp which unwrap will use to return * information such as the Quality-of-Protection that was * applied to the wrapped token, whether or not it was * encrypted, etc. Since the initial MessageProp values * are ignored, it doesn't matter what they are set to. */ MessageProp prop = new MessageProp(0, false); /* * Read the token. This uses the same token byte array * as that used during context establishment. */ token = new byte[inStream.readInt()]; System.out.println("Will read token of size " + token.length); inStream.readFully(token); byte[] bytes = context.unwrap(token, 0, token.length, prop); String str = new String(bytes); System.out.println("Received data \"" + str + "\" of length " + str.length()); System.out.println("Encryption applied: " + prop.getPrivacy());
次にSampleServerは、復号化されたメッセージのMICを生成して、SampleClientに送信します。これは、必須というわけではなく、復号化されたメッセージのMICの生成を示しているだけです。復号化されたメッセージは、SampleClientがラップしてSampleServerに送信した元のメッセージと正確に同じになるはずです。SampleServerがこれを生成してSampleClientに送信し、その後SampleClientによる検証が行われると、SampleServerが保持する復号化済のメッセージは、SampleClientから送信された元のメッセージと正確に一致することが保証されます。
/* * First reset the QOP of the MessageProp to 0 * to ensure the default Quality-of-Protection * is applied. */ prop.setQOP(0); token = context.getMIC(bytes, 0, bytes.length, prop); System.out.println("Will send MIC token of size " + token.length); outStream.writeInt(token.length); outStream.write(token); outStream.flush();
次のSampleClientコードは、SampleServerにより計算された(復号化済みメッセージに対する) MICを読み取って、このMICが元のメッセージのMICなのかどうかを検証します。これにより、SampleServerが保持する復号化されたメッセージが、元のメッセージと同じであることが保証されます。
token = new byte[inStream.readInt()]; System.out.println("Will read token of size " + token.length); inStream.readFully(token); /* * Recall messageBytes is the byte array containing * the original message and prop is the MessageProp * already instantiated by SampleClient. */ context.verifyMIC(token, 0, token.length, messageBytes, 0, messageBytes.length, prop); System.out.println("Verified received MIC for message.");
socket.close(); context.dispose();
このチュートリアルでは、ベースとなる認証およびセキュアな通信技術としてKerberos V5が使用されているため、ユーザーまたはサービスが要求される場合、常にKerberosスタイルのプリンシパル名が使用されます。
たとえば、SampleClientを実行する場合、ユーザー名の指定が求められます。Kerberosスタイルのユーザー名は、Kerberos認証用だけに割り当てられたユーザー名です。このユーザー名は、ベース・ユーザー名(例、「mjones」)、「@」、およびレルムの順序で構成されます(例、「mjones@KRBNT-OPERATIONS.EXAMPLE.COM」)。
通常、SampleServerなどのサーバー・プログラムは、「サービス」を提供し、特定の「サービス・プリンシパル」に代わって実行されるプログラムと見なされます。SampleServerのサービス・プリンシパル名が必要とされるのは、次の場合です。
このドキュメントおよび関連するログイン構成ファイルを通じて、
service_principal@your_realmという形式で記述された部分は、自分の環境で使用する実際の名前で置き換えてください。サービス・プリンシパル名として、任意の Kerberosプリンシパルを実際に使用できます。このため、このチュートリアルを実行してみる場合、クライアント・ユーザー名とサービス・プリンシパル名の両方に自分のユーザー名を使用できます。
通常、本番稼動環境では、システム管理者は、サーバーを特定のプリンシパルのみで実行し、特定の名前を割り当てて使用します。たいてい、割り当てるKerberos形式のサービス・プリンシパル名は、次のようになります。
service_name/machine_name@realm;
たとえば、「KRBNT-OPERATIONS.EXAMPLE.COM」というレルム内の「raven」という名前のマシンでnfsサービスを実行する場合、サービス・プリンシパル名は次のようになります
nfs/raven@KRBNT-OPERATIONS.EXAMPLE.COM
ただし、このようなマルチコンポーネント名は必須ではありません。ユーザー・プリンシパル名のような、シングルコンポーネント名も使用できます。たとえば、インストールによって、レルム内のすべてのftpサーバーで同じftpサービス・プリンシパルftp@realm
を使用する場合と、ftpサーバーごとに異なるftpプリンシパルを使用する(たとえば、マシンhost1
、host2
のftpプリンシパルがそれぞれftp/host1@realm
、ftp/host2@realm
となる)場合があります。
ユーザーまたはサービス・プリンシパル名のレルムがデフォルト・レルムの場合は(「Kerberos要件」を参照)、Kerberosにログインする際、ユーザー名を求めるプロンプトが表示された時点で、必ずしもレルムを指定する必要はありません。このため、たとえばユーザー名が「mjones@KRBNT-OPERATIONS.EXAMPLE.COM」で、SampleClientを実行する場合、ユーザー名が要求されたら、レルムを省略して「mjones」とだけ入力できます。名前はKerberosプリンシパル名のコンテキストで解釈され、必要に応じてデフォルトのレルムが付けられます。
GSSManagerのcreateName
メソッドにより、プリンシパル名がGSSNameに変換される場合にも、レルムの指定を省略できます。たとえば、SampleClientの実行時に、引数の1つにサービス・プリンシパル名を指定します。この場合、SampleClientが名前をcreateName
メソッドに渡し、このメソッドが必要に応じてデフォルトのレルムを追加するため、名前を指定する際にレルムを省略できます。
プリンシパル名をログイン構成ファイルおよびポリシー・ファイルで使用する場合は、常にレルムを含めて名前を指定することをお薦めします。理由は、これらのファイルのパーサーの動作が実装に依存しないため、プリンシパル名の使用前にデフォルトのレルムが追加される場合と、追加されない場合があるためです。名前にレルムが指定されていない場合、以降のアクションは失敗します。
このチュートリアルでは、JAASメソッドを直接的(「JAAS認証」および「JAAS承認」チュートリアルの場合のように)または間接的(たとえば、「JAAS Loginユーティリティの使用」チュートリアルや「JAAS LoginユーティリティおよびJava GSS-APIを使用したセキュアなメッセージ交換」チュートリアルで解説されているLoginユーティリティを介して)に呼び出すのではなく、基盤となるKerberosメカニズムがSampleClientおよびSampleServerを実行するユーザーのクレデンシャルを取得するようにしました。
Sun Microsystemsの提供するKerberosメカニズムのデフォルト実装では、Kerberos名およびパスワードの入力が求められ、Kerberos KDCに対して指定されたユーザーまたはサービスの認証が行われます。メカニズムでは、この認証処理にJAASが使用されています。
JAASはプラガブル認証フレームワークをサポートします。これは、呼出し側アプリケーションに任意の型の認証モジュールをプラグインできるということです。特定のアプリケーションで使用されるログイン・モジュールは、ログイン構成によって指定されます。Sun Microsystems提供のデフォルトのJAAS実装では、ファイル内にログイン構成情報を指定する必要があります。(注: ベンダーによってはファイルベースの実装が提供されない場合もあります。)ログイン構成ファイルとその内容、および使用するログイン構成ファイルの指定方法については、「JAASログイン構成ファイル」を参照してください。
このチュートリアルでは、構成ファイル内にKerberosログイン・モジュールcom.sun.security.auth.module.Krb5LoginModule
が指定されます。このログイン・モジュールは、Kerberos名とパスワードの入力を求めるプロンプトを表示し、Kerberos KDCに対する認証処理を実行しようとします。
ログイン構成ファイルに、クライアント側が使用するエントリとサーバー側が使用するエントリの2つが含まれる場合、SampleClientとSampleServerの両方で同じログイン構成ファイルを使用できます。
このチュートリアルで使用するログイン構成ファイルbcsLogin.confを、次に示します。
com.sun.security.jgss.initiate { com.sun.security.auth.module.Krb5LoginModule required; }; com.sun.security.jgss.accept { com.sun.security.auth.module.Krb5LoginModule required storeKey=true };
SunのGSS-APIメカニズムの実装では、新しいクレデンシャルが必要な場合、com.sun.security.jgss.initiate
という名前のエントリとcom.sun.security.jgss.accept
という名前のエントリが使用されます。このチュートリアルではKerberos V5メカニズムを使用するため、これらのクレデンシャルの取得にKerberosログイン・モジュールの呼出しが必要です。このため、エントリ内にKrb5LoginModuleを必須モジュールとして記載してあります。com.sun.security.jgss.initiate
エントリはクライアント側の構成を指定し、com.sun.security.jgss.accept
エントリはサーバー側の構成を指定します。
Krb5LoginModuleが成功するのは、指定されたエンティティでのKerberos KDCへのログインが成功した場合だけです。SampleClientまたはSampleServerの実行時、ユーザーは名前とパスワードの入力を求められます。
SampleServerのエントリstoreKey=true
は、ログイン時に指定されたパスワードから秘密鍵を計算すること、およびログインにより作成されたサブジェクトのprivateクレデンシャルに秘密鍵を格納することを意味します。この鍵は、SampleClientとSampleServerとの間でセキュリティ・コンテキストを確立する際、相互認証に利用されます。
Krb5LoginModuleに引渡し可能なすべてのオプションの詳細は、Krb5LoginModuleドキュメントを参照してください。
このチュートリアルでは、システム・プロパティjavax.security.auth.useSubjectCredsOnly
をfalse
に設定します。この設定により、JAASによって設定された既存のサブジェクトから必要なクレデンシャルを取得する際、GSSメカニズムを必要とする通常の制限が緩和されます。この制限緩和により、メカニズムが、ベンダー固有の位置からクレデンシャルを取得できるようになります。たとえば、オペレーティング・システムのキャッシュ(存在する場合)の使用を選択するベンダーや、ディスク上の保護されたファイルから読取りを選択するベンダーがあります。
この制限が緩和された場合でも、Sun MicrosystemのKerberosメカニズムにより、スレッドのアクセス制御コンテキストに関連付けられたサブジェクト内のクレデンシャルを検索します。ただし、クレデンシャルが見つからなかった場合、Kerberosモジュールを使用してJAAS認証を実行し、新規クレデンシャルを取得します。Kerberosモジュールは、Kerberosプリンシパル名とパスワードの入力を求めます。他のベンダーが提供するKerberosメカニズムの実装では、このプロパティをfalse
に設定した場合の動作が異なる場合があることに注意してください。各実装の動作を判断するには、それらのドキュメントを参照してください。
SampleClientおよびSampleServerプログラムを実行するには、次の操作を行います。
SampleServerの実行準備では、次の操作を行います。
SampleServer.java
をコンパイルします。
javac SampleServer.java
SampleClientの実行準備では、次の操作を行います。
SampleClient.java
をコンパイルします。
javac SampleClient.java
SampleClientを実行する前に、必ずSampleServerを実行してください。SampleClientはSampleServerへのソケット接続を試みるため、SampleServerが稼動していないとソケット接続が受け付けられず、失敗します。
SampleServerを実行する場合、SampleServerを稼動する予定のマシンで実行してください。このマシン名(ホスト名)は、SampleClientの引数として指定します。サービス・プリンシパル名は、ログイン構成ファイルやポリシー・ファイルなど、いくつかの場所に表示されます。
SampleServerの実行用に準備したディレクトリに移動します。次を指定して、SampleServerを実行します。
-Djava.security.krb5.realm=<your_realm>
(使用するKerberosレルム)。たとえば、レルムが「KRBNT-OPERATIONS.EXAMPLE.COM」の場合、-Djava.security.krb5.realm=KRBNT-OPERATIONS.EXAMPLE.COM
のように指定します。-Djava.security.krb5.kdc=<your_kdc>
(使用するKerberos KDC)。たとえば、KDCが「samplekdc.example.com」の場合、-Djava.security.krb5.kdc=samplekdc.example.com
のように指定します。-Djavax.security.auth.useSubjectCredsOnly=false
(ベースとなるメカニズムによるクレデンシャルの取得方法の指定)。「useSubjectCredsOnlyシステム・プロパティ」を参照してください。-Djava.security.auth.login.config=bcsLogin.conf
。使用するログイン構成ファイルとしてbcsLogin.conf
を指定します。SampleServerに必要な引数は、クライアント接続の待機に使用するポート番号だけです。通常は使用しない大きなポート番号であれば、どの番号でも選択できます。(例、4444)。
次に、Microsoft WindowsおよびSolaris、LinuxおよびMac OS Xシステムで使用するすべてのコマンドを示します。
重要: このコマンドの、<port_number>
を適切なポート番号に、<your_realm>
を使用するKerberosレルムに、<your_kdc>
を使用するKerberos KDCにそれぞれ置き換えてください。
次に、コマンドを示します。
java -Djava.security.krb5.realm=<your_realm> -Djava.security.krb5.kdc=<your_kdc> -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.auth.login.config=bcsLogin.conf SampleServer <port_number>
コマンド全体は、1行で(または、Solaris、LinuxまたはMac OS Xシステムでは、最後の行を除く各行が、続いていることを示す「\」で終わる複数行で)表示されるようにしてください。ここでは、読みやすくするために複数行に分けて表示してあります。このコマンドは非常に長いため、.batファイル(Windowsの場合)または.shファイル(Solaris、LinuxまたはMac OS Xの場合)に記述して、そのファイルを実行して、コマンドを実行する必要がある場合があります。
SampleServer
コードは、指定されたポート上でソケット接続を待機します。入力を求められたら、サービス・プリンシパルのKerberos名およびパスワードを入力してください。ログイン構成ファイルで指定された基盤となるKerberos認証メカニズムにより、サービス・プリンシパルのKerberosへのログインが行われます。
ログイン時のトラブルシューティングについては、「トラブルシューティング」を参照してください。
SampleClientを実行するため、最初にSampleClientの実行準備を行なったディレクトリに移動します。次を指定して、SampleClientを実行します。
-Djava.security.krb5.realm=<your_realm>
(使用するKerberosレルム)。-Djava.security.krb5.kdc=<your_kdc>
(使用するKerberos KDC)。-Djavax.security.auth.useSubjectCredsOnly=false
(ベースとなるメカニズムによるクレデンシャルの取得方法の指定)。-Djava.security.auth.login.config=bcsLogin.conf
。使用するログイン構成ファイルとしてbcsLogin.conf
を指定します。SampleClientの引数は、(1) SampleServerを表すサービス・プリンシパルのKerberos名、(2) SampleServerを実行するホスト(マシン)の名前、(3) SampleServerがクライアント接続を待機するポート番号です。
次に、WindowsおよびSolaris、LinuxおよびMac OS Xシステムで使用するすべてのコマンドを示します。
重要: このコマンドの、<service_principal>
、<host>
、<port_number>
、<your_realm>
、および<your_kdc>
を、適切な値に置き換えてください (ポート番号は、SampleServerの引数として渡したポート番号と同じにする必要がある)。値を引用符で囲む必要はありません。
次に、コマンドを示します。
java -Djava.security.krb5.realm=<your_realm> -Djava.security.krb5.kdc=<your_kdc> -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.auth.login.config=bcsLogin.conf SampleClient <service_principal> <host> <port_number>
コマンド全体を1行で入力してください。ここでは、読みやすくするために複数行に分けて表示してあります。SampleServerを実行するコマンドと同様、コマンド・ウィンドウに直接入力するにはコマンドが長すぎる場合、.batファイル(Windowsの場合)または.shファイル(Solaris、LinuxおよびMac OS Xの場合)に記述して、そのファイルを実行してください。
入力が求められたら、Kerberosユーザー名およびパスワードを入力します。ログイン構成ファイルで指定された基盤となるKerberos認証メカニズムにより、Kerberosへのログインが行われます。SampleClientコードは、SampleServerへのソケット接続を要求します。SampleServerが接続を受け付けると、SampleClientおよびSampleServerにより、このチュートリアルで解説した方法で、共有コンテキストの確立およびメッセージの交換が行われます。
ログイン時のトラブルシューティングについては、「トラブルシューティング」を参照してください。