レベル: 中級 Vladimir Silva (vsilva@us.ibm.com), Software engineer, IBM
2007年 2月 20日 Grid Security Infrastructure (GSI) は、Java™ Generic Security Services (GSS-API) の実装の 1 つです。GSS は、互いに通信するアプリケーションの間でメッセージを安全に交換するために使用されます。GSS では、Kerberos など、基礎となるさまざまなセキュリティー・メカニズムに基づいたセキュリティー・サービスに一様にアクセスできます。この記事では、GSI/GSS-API 拡張機能とプロキシー証明書を利用して独自のクライアント/サーバー・アプリケーションを構築する方法について学びます。これは、グリッド・ミドルウェアで使用される基本認証メカニズムになります。
impersonation による認証
プロキシー証明書は、一種の偽装 (impersonation) によるクレデンシャルです。impersonation はセキュリティー手法の 1 つで、impersonation により、エンティティー A は別のエンティティー (エンティティー B) に、A であるかのようにして他のエンティティーを認証する権利を付与することができます。言い換えれば、B は A になりすましています。
それではなぜ、impersonation が望ましい手法なのでしょうか? それは、分散ネットワークで認証を行うときの 2 つの大きな問題に対処できるからです。
-
シングル・サインオン -- なぜシングル・サインオンがそれほど重要なのでしょうか? 例えば、あるユーザーが複数のリソースでプロセスを実行しなければならないとします。その場合、シングル・サインオンを使用すれば、認証は 1 回ですみます。リソースごとに何度も認証を行う必要はありません。
-
権限委譲 -- プロセスがユーザーの代わりに認証を行わなければならないために、権限委譲が求められることがあります。そのため、必要な権限がプロセスに権限委譲されなければなりません。
例えば、あるユーザーが 2 つのホスト間でリモート実行サービスを開始したとします。この場合、このサービスは、ユーザーに代わってシングル・サインオンを使って、リソースに対して認証を行う必要があります。そして認証が済んだら、このサービスは、これらのホスト同士が認証できるようにこれらのホストに権限を権限委譲しなければなりません。これらを実現するには、プロキシー証明書を使用します。
相互認証
デジタル証明書を持つ 2 つの団体が、そのデジタル証明書に署名した認証局 (CA) を信頼する場合、両方の団体が相互認証を行えば、互いが互いを本物であると証明できます。署名を行った CA を信頼するということは、実際には、CA の証明書のコピー (公開鍵を含む) を所有していなければならず、およびその証明書が確かにその CA のものであると信頼するということです。相互認証は、次のようにして、2 つのエンティティー (A と B) の間で行われます。
- A が B との接続を確立します。認証プロセスを開始するために、A が B に A の証明書を付与します。この証明書には、A の識別情報、公開鍵、および証明書の認定に使用されている CA という 3 つの項目が含まれています。
- B は CA のデジタル署名を調べて、その CA が実際に署名を行ったこと、およびその証明書が改ざんされていないことを確認します。そうすることで、B は、その証明書が有効であることを確認します。(B は、A の証明書に署名した CA を信頼する必要があります。)
- B は、A がその証明書で特定される実際の人物であることを確認するために、無作為にメッセージを作成して A に送信し、そのメッセージを暗号化するように A に依頼します。A は A の秘密鍵を使用してメッセージを暗号化し、B に送り返します。B は A の公開鍵を使用してメッセージを復号化します。この結果、無作為に作成された元のメッセージが復元されれば、B は A が本人であることを認識します (B は A の身元を信頼します)。
- 3 番目のステップで行った操作を、今度は A と B を逆にして行います。
こうすることで、A と B は相互認証されます。
プロキシー証明書
プロキシー証明書は新規の証明書でできています。そこには、新規の公開鍵と新規の秘密鍵が含まれています。この新規の証明書には、CA ではなく所有者によって署名された所有者の識別情報が、変更された形で組み込まれています。プロキシー証明書には、次の特性があります。
- 有効期間 -- 特定の期間が経過すると、プロキシーは無効になります。
- 暗号化されていない秘密鍵 -- プロキシーは長時間にわたって有効というわけではないため、プロキシーの秘密鍵を所有者の秘密鍵と同じくらい安全にしておく必要はありません。そのため、ファイルのアクセス権を使用して他のユーザーが参照できないようにしてあれば、暗号化されていない秘密鍵とともにプロキシー証明書を一時的な場所に保管することができます。
ユーザーは、プロキシー証明書と秘密鍵を一度作成してしまえば、そのプロキシー証明書と秘密鍵を使用して、パスワードなしで相互認証を行うことができます。
プロキシー証明書は、Globus プロジェクトによって開発された Transport Layer Security (TLS) プロトコルの拡張機能です。Globus は、GSI プロキシーを他の TLS ソフトウェア一緒に使用できるように、Global Grid Forum と協力して、プロキシーを TLS の標準拡張機能にしようとしています。
独自の GSI 対応クライアント・サーバーを構築する
これから構築するアプリケーションの名前は、Client と Server です。これらのアプリケーションは Java プログラミング言語で作成されており、Commodity Grid (CoG) Kit に付属の GSI 実装およびサポート・ライブラリーが必要となります。独自の GSI 対応アプリケーションの構築は簡単で、Client と Server の構築手順は、次のようになります。
- コマンド・ラインの引数を読み取る
- クライアントとサーバーの間に転送用のソケット接続を確立する
- プロキシー・クレデンシャルをロードする
- セキュリティー・コンテキストを設定する
- 必要に応じて、安全にメッセージを交換する
- クリーンアップを行う
コマンド・ラインの引数を読み取る
Client と Server のメインの方法で最初に行う最も簡単な作業が、コマンド・ラインの引数の読み取りです。
Client は 2 つの引数 (接続先のホスト名とポート) を要求します。
リスト 1. Client のホスト名とポート
// load arguments
if (args.length < 2)
{
System.err.println("Usage: java {options} Client "
+ " {hostName} {port}");
System.exit(-1);
}
String hostName = args[0];
int port = Integer.parseInt(args[1]);
|
Server は 1 つの引数 (接続をリッスンするポート番号) を要求します。
リスト 2. Server のポート番号
// read the command-line arguments
if (args.length != 1) {
System.err.println("Usage: java {options} Server {localPort}");
System.exit(-1);
}
int localPort = Integer.parseInt(args[0]);
|
ソケット接続を確立する
Java GSS-API には、トークン (不明な型のバイト・データ) を作成したり解釈したりするためのメソッドが用意されています。このトークンには、2 つのピアの間で安全に交換されるメッセージが含まれていますが、実際にトークンが転送される方法はピアによって異なります。ここでは、クライアントとサーバーの間でソケット接続を確立し、ソケット・ストリームとセキュリティー・コンテキストから構成されるストリームを使用してデータを交換します。
Client は、次のようにして、Server へのソケット接続を確立し、入出力用のストリームを抽出する必要があります。
リスト 3. Server へのソケット接続を確立する Client
Socket socket = new Socket(hostName, port);
DataInputStream inStream =
new DataInputStream(socket.getInputStream());
DataOutputStream outStream =
new DataOutputStream(socket.getOutputStream());
System.out.println("Client: Connected to server "
+ socket.getInetAddress());
|
サーバー・アプリケーションは、ポートをリッスンするための ServerSocket を作成し、このポートは、引数として指定されます (次の例を参照)。
ServerSocket ss = new ServerSocket(localPort);
|
その際、ServerSocket は、クライアントからの接続を待機してから受け入れ、クライアントとデータを交換する場合に備えて入出力ストリームを初期化します。
リスト 4. クライアントからの接続を待機してから受け入れる ServerSocket
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());
|
Socket オブジェクトは、クライアントとの通信に使用されます。このオブジェクトは、ServerSocket上で他のクライアントからの接続要求を引き続きリッスンすることができます。通常、これにはループが使用されます (次のリストを参照)。
リスト 5. クライアントと通信する Socket オブジェクト
while (true) {
Socket socket = ss.accept();
// Get input and output streams for the connection
// Create a context with the client
// Exchange messages with the client
// Clean up
}
|
このループで処理できるクライアントは一度に 1 つのみです。ただし、スレッドを使用すれば、サーバーが同時に複数のクライアントを処理するように変更できます。
プロキシー・クレデンシャルをロードする
CoG Kit はクライアント API です。グリッド・アプリケーションの開発者や管理者は、この API を使用することにより、さらに高度なフレームワークを利用して、グリッドを使用したり、プログラミングしたり、管理したりできます。CoG には、プロキシー証明書など、多くのオブジェクトをロードしたり作成したりするための API が備わっています。例えば、CoGProperties.getProxyFile() メソッドを呼び出すと、grid-proxy-init を使用してすでに作成されているプロキシー証明書へのパスが返されます。次に、クレデンシャルがバッファー (バイト) にロードされ、GSI 認証のために GSSCredential に変換されます。
CoGProperties cog = CoGProperties.getDefault();
byte proxyBytes[] = readBinFile( cog.getProxyFile() );
|
次のステップでは、ExtendedGSSManager オブジェクトのインスタンスを取得します。このクラスは、他の重要な GSS-API クラスのファクトリーとして機能し、サポートしているメカニズムに関する情報を提供します。また、3 つの GSS-API インターフェース (GSSName、GSSCredential、および GSSContext) を実装するクラスのインスタンスを作成することができ、使用可能なメカニズムと、それぞれのメカニズムでサポートされている名前タイプのリストを照会するためのメソッドが備わっています。静的メソッド getInstance を使用すれば、デフォルトのサブクラス ExtendedGSSManager のインスタンスを取得することができます。
ExtendedGSSManager manager =
(ExtendedGSSManager)ExtendedGSSManager.getInstance();
|
次のステップでは、一連のメカニズムを使用してクレデンシャルを取得するファクトリー・メソッドを呼び出します。
リスト 6. 証明書を取得するファクトリー・メソッドを呼び出す
GSSCredential credential = manager.createCredential(
proxyBytes, // proxy data
ExtendedGSSCredential.IMPEXP_OPAQUE,
GSSCredential.DEFAULT_LIFETIME, // default life time
null, // OID Mechanism
GSSCredential.INITIATE_AND_ACCEPT);
System.out.println("Client Credential: "
+ credential.getName()
+ " Remaining life time:"
+ credential.getRemainingLifetime());
|
引数は次のようになっています。
- grid-proxy-init などのコマンドで作成したプロキシー・クレデンシャルのバッファーまたはバイト配列をエクスポートしたもの
- ExtendedGSSCredential.IMPEXP_OPAQUE は、エクスポートしたバッファーが、メモリーやディスクに保管したり、別のプロセスに渡したりする場合に適した、不明な型のバッファーであることを示しています。
- 有効期間の値 - この場合は、デフォルト値が使用されます。
- エクスポートされたクレデンシャルに望ましいメカニズム - システム・デフォルト値を示すnull が使用されるでしょう。
- クレデンシャル使用フラグ - この場合、INITIATE_AND_ACCEPT は、コンテキストを初期化して受け入れられるようにするために、クレデンシャルを使用可能にすることを要求します。
セキュリティー・コンテキストを設定する
2 つのアプリケーションで、Java GSS-API を使用してメッセージを安全に交換するには、証明書を使用して共有セキュリティー・コンテキストを設定する必要があります。
Client の ExtendedGSSManager.createContext は、通信を開始する側でコンテキストを作成するためのファクトリー・メソッドです。最初の引数は、ターゲット・ピアの名前です。null は、基礎となる認証メカニズムによって指定されるデフォルト値を示しています。2 番目の引数は、そのメカニズムのオブジェクト ID (OID) です (繰り返しになりますが、null は、デフォルト値が使用されることを意味します)。3 番目の引数は、前のステップの GSS クレデンシャルです。最後の引数は、コンテキストのデフォルト有効期間です。
リスト 7. 共有セキュリティー・コンテキストを設定する
GSSContext context = null;
GSIGssOutputStream gssout = null;
GSIGssInputStream gssin = null;
context = manager.createContext(null,
null,
credential,
GSSContext.DEFAULT_LIFETIME);
|
コンテキストをインスタンス化したら、コンテキスト・アクセプターでコンテキストを実際に設定する前に、希望のセキュリティー・コンテキスト特性を決定するさまざまなオプション (下記に示します) をコンテキスト・イニシエーターで設定できます。
-
相互認証 - コンテキスト・イニシエーターは、アクセプターに対して必ず認証されます。イニシエーターが相互認証を要求すると、アクセプターもイニシエーターに対して認証されます。
-
機密性 - 機密性を要求するということは、コンテキスト・メソッド wrap の暗号化を有効にするように要求するということです。
-
完全性 - ここでは、wrap メソッドと getMIC メソッドの完全性を要求します。完全性が要求されるときに、これらのメソッドが呼び出されると、Message Integrity Code (MIC) として知られる暗号タグが生成されます。
context.requestCredDeleg(false);
context.requestMutualAuth(true);
|
Server 側で必要な引数は証明書のみです。クライアントとの間でデータを送受信するには、GSI 入出力ストリームを取得する必要があります。
GSSContext context = manager.createContext(credential);
GSIGssOutputStream gssOut = new GSIGssOutputStream(outStream, context);
GSIGssInputStream gssIn = new GSIGssInputStream(inStream, context);
|
Client は GSSContext をインスタンス化して必要なコンテキスト・オプションを指定した後に、実際に Server に関するセキュリティー・コンテキストを設定できます。それには、対話ごとにループが使用されます。
- コンテキストの initSecContext メソッドを呼び出す -- 今回が初めての呼び出しの場合、このメソッドには null のトークンが渡されます。それ以外の場合は、Server から Client に送信された最新のトークンが渡されます (このトークンは、Server が acceptSecContext を呼び出すことによって生成されます)。
- initSecContext から返されるトークンがある場合、そのトークンを Server に送信する -- initSecContext が初めて呼び出されるときに、必ずトークンが生成されます。最後の呼び出しでは、トークンは返されないことがあります。
- コンテキストが設定されているかどうか確認する -- コンテキストが設定されていない場合、Client は Server から別のトークンを受信して、次のループを繰り返し始めます。
initSecContext から返されるトークンや、Server から受信されるトークンは、バイト配列で保管され、不明な型のデータとしてクライアントとサーバーで処理されます。このデータは、クライアントとサーバーの間で受け渡しされ、Java GSS-API メソッドにより解釈されます。メッセージの交換には、一連の GSI 入出力ストリームが必要です。
リスト 8. GSI 入出力ストリーム
gssout = new GSIGssOutputStream(outStream, context);
gssin = new GSIGssInputStream(inStream, context);
byte [] inToken = new byte[0];
byte [] outToken = null;
/*
* Establish a security context
* 1. Client: sends a secure handshake token
* 2. Server: receives handshake token
* 3. Server: Sends hanshake token back to the client
* 4. Client: receives handshake. Security context is established.
*/
while( !context.isEstablished() ) {
outToken = context.initSecContext(inToken, 0, inToken.length);
if (outToken != null) {
gssout.writeToken(outToken);
}
if (!context.isEstablished()) {
inToken = gssin.readHandshakeToken();
}
}
System.out.println("Client: Security context Established! ");
|
一方、サーバー・コンテキスト・ループでは、対話の方法が多少異なります。
- Client からトークンを受信する -- このトークンは、Client が initSecContext を呼び出した結果として生成されます。
- コンテキストの acceptSecContext メソッドを呼び出し、受信したトークンに渡す。
- acceptSecContext がトークンを返すと、Server はこのトークンを Client に送信し、コンテキストがまだ設定されていなければ次のループを繰り返し始める。
リスト 9. トークンを Client に送信し、次のループを繰り返し始める Server
while (!context.isEstablished())
{
token = gssIn.readHandshakeToken();
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("Server: Will send token of size "
+ token.length
+ " from acceptSecContext.");
gssOut.writeToken(token);
}
}
System.out.println("Server: Context Established! ");
|
安全にメッセージを交換する
Client と Server の間でセキュリティー・コンテキストが設定されると、Client と Server はこのコンテキストによりメッセージを安全に交換できます。安全に交換するためのメッセージを準備するメソッドは、wrap と getMIC の 2 つがあります。実際には、wrap メソッドは 2 つあり (getMIC メソッドも 2 つあり)、2 つのメソッドの違いは、入力メッセージの場所 (バイト配列または入力ストリーム) を示しているものと、出力先 (バイト配列の戻り値または出力ストリーム) を示しているものとの違いです。
-
wrap はメッセージ交換の主要なメソッドです。署名は byte[] wrap (byte[] inBuf, int offset, int len, MessageProp msgProp) となります。ここで、(inBuf) は送信するメッセージを、(offset) はメッセージの開始位置を、(len) はメッセージの長さを、(MessageProp) は希望の Quality-of-Protection (QOP) の指示と、暗号化が必要かどうかの指定を表しています。QOP の値により、使用される暗号保全性アルゴリズムや、必要に応じて暗号化アルゴリズムが選択されます。さまざまな QOP の値に対応するアルゴリズムが、基礎となるメカニズムを提供する側から指定されます。例えば、Kerberos V5 の値は、RFC 1964 で定義されています。一般に、デフォルトの QOP を要求するには、QOP の値に 0 を指定します。
-
getMIC は、指定のメッセージに対して暗号化 MIC が含まれたトークンを取得するときに使用されます。通常、そのトークンは、ピアとの間で同じデータを持っていることを確認する際に、互いにデータそのものを送信するというコストをかけずに、MIC を送信するだけで確認を行うために使われます。署名は byte[] getMIC (byte[] inMsg, int offset, int len, MessageProp msgProp) となります。ここで、(inMsg) は送信するメッセージを、(offset) はメッセージの開始位置を、(len) はメッセージの長さを表しています。また、(MessageProp) を渡して、希望の QOP を指示することもできます。一般に、デフォルトの QOP を要求するには、QOP の値に 0 を指定します。
クリーンアップを行う
Client と Server がソケットを閉じ、システム・リソースと、コンテキスト・オブジェクトに保管されている暗号情報を解放し、コンテキストを無効にすることで、クリーンアップ処理が行われます。
リスト 10. ソケットを閉じ、システム・リソースと暗号情報を解放する
/*
* If mutual authentication did not take place, then only the
* client was authenticated to the server. Otherwise, both
* client and server were authenticated to each other.
*/
if (context.getMutualAuthState())
System.out.println("Mutual authentication took place!");
context.dispose();
socket.close();
|
新規 GSI アプリケーションをテストする
新規 GSI クライアント・サーバーをテストする前に、最初のステップとして、CoG Kit (さらには、Globus Toolkit) のような GSI 対応ツールを使用してプロキシー証明書を作成します。
図 1. CoG Kit を使用して grid-proxy-init を呼び出したときの出力
この記事でデモンストレーションするアプリケーションは、Eclipse Java プロジェクトとして配布されています。このプロジェクトを皆さんのワークスペースにインポートして、次の作業を行ってください。
- Server を開始する。
- Client を実行する。
- Client と Server のコンソール出力を参照して、相互認証を確認する。
出力は次のようになります。
新規 GSI アプリケーションをテストする
Client: Connected to server localhost/127.0.0.1
Client: Loaded proxy file: C:\DOCUME~1\Owner\LOCALS~1\Temp\x509up_u_owner
Client Credential: /C=US/L=Raleigh/O=ACME/OU=IT/CN=Vladimir Silva Remaining life time:9349
Client: Security context Established!
Client is /C=US/L=Raleigh/O=ACME/OU=IT/CN=Vladimir Silva
Server is /C=US/L=Raleigh/O=ACME/OU=IT/CN=Vladimir Silva
Client: Mutual authentication took place!
Server Credential: /C=US/L=Raleigh/O=ACME/OU=IT/CN=Vladimir Silva
Server: Waiting for incoming connection...
Server: Got connection from client /127.0.0.1
Server: Will send token of size 1480 from acceptSecContext.
Server: Will send token of size 75 from acceptSecContext.
Server: Context Established!
Client is /C=US/L=Raleigh/O=ACME/OU=IT/CN=Vladimir Silva
Server is /C=US/L=Raleigh/O=ACME/OU=IT/CN=Vladimir Silva
Server: Mutual authentication took place!
Server: Closing connection with client /127.0.0.1
Server: Waiting for incoming connection...
|
期待どおりの出力が得られない場合は、次の「トラブルシューティング」セクションを参照して、問題のある箇所を突きとめてください。
トラブルシューティング
最も単純な問題の 1 つは、プロキシーが無効であったり期限が切れていたりする場合に発生します。この原因としては、CoG/GT4 ツールキットをインストールする際のエラーや、証明書の破損、入出力エラーなどが考えられます。よくあるエラー・メッセージを次に示します。
リスト 12. エラー・メッセージ
Got connection from client /127.0.0.1
GSSException: Expired credentials detected
at org.globus.gsi.gssapi.GlobusGSSManagerImpl
.createCredential(GlobusGSSManagerImpl.java:118)
at org.globus.gsi.gssapi.GlobusGSSManagerImpl
.createCredential(GlobusGSSManagerImpl.java:64)
...
|
通常、無効なセキュリティー・コンテキストは、コンテキスト・ループ内のコーディング・エラーが原因で生成されます (GSS と GSI のメソッドが同じように使用されている場合など)。GSI は GSS API の拡張機能なので、GSS クライアントは、GSI サーバーに関するコンテキストを設定できません。
リスト 13. エラー・メッセージ
Waiting for incoming connection...
Got connection from client /127.0.0.1
unwrap failed. Caused by GSSException:
Security context init/accept not yet called or context deleted
at org.globus.gsi.gssapi.GlobusGSSContextImpl
.checkContext(GlobusGSSContextImpl.java:1283)
at org.globus.gsi.gssapi.GlobusGSSContextImpl
.unwrap(GlobusGSSContextImpl.java:831)
|
まとめ
Grid Security Infrastructure (GSI) は、Java Generic Security Services (GSS-API) の拡張機能です。GSI は、セキュリティー・サービスに一様にアクセスできるようにすることで、2 つのアプリケーション間でメッセージを安全に交換するために使用することができます。この記事の目的は、単純な GSI 対応クライアント/サーバー・アプリケーションを構築して、グリッド・アプリケーションの基本認証メカニズムをデモンストレーションすることでした。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Sample GSI client-server code | gr-gt4mutualauth.zip | 19KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
-
IBM 試用版ソフトウェアを使用して最新のオープン・ソース開発プロジェクトを導入してください。試用版ソフトウェアはダウンロードすることも DVD から入手することもできます。
議論するために
著者について  | |  | Vladimir Silvaはエクアドルのキトーの生まれです。1994年に陸軍工科学校でシステムアナリストの学位を取得し、同年交換留学生として、ミドルテネシー州立大学でコンピューターサイエンスの経験を積むべく渡米しました。卒業後は、IBMに入社し、Web-Aheadテクノロジーの研究機関に所属しました。グリッド・コンピューティング, ニューラルネット, 人工知能に興味を持っており、OCPやMCSD,MCPなどを含め、数え切れない程のIT認定証を持っています。彼の連絡先はvsilva@us.ibm.com、またはvladimir_silva@yahoo.comです。 |
記事の評価
|