ネイティブGSS-APIへのアクセス

Javaプラットフォーム・アプリケーションとネイティブ・アプリケーションとのシームレスな統合の実現を支援するため、JDKでは暗号化メカニズムを独自実装するかわりに、構成した場合はネイティブGSS-APIを使用するようにJava GSS-APIが拡張されています。ネイティブGSS-APIとその基礎となるネイティブ暗号化メカニズムを使用すると、ユーザーの環境内のネイティブ・クレデンシャルおよび設定が自動的に選択されます。これは、Java GSS-APIでは暗号化メカニズムの独自実装が使用されるデフォルト・ケースとは異なります。Kerberosを使用している場合、Java GSS-APIが機能するためには、指定されたKerberosシステム・プロパティを使用して、JavaアプリケーションがKerberos構成情報を提供する必要があります。デフォルト・ケースの詳細は『JAASおよびJava GSS-APIチュートリアルの紹介』で説明されているので、この項ではネイティブGSS-APIを使用するためのJava GSS-APIの有効化または構成方法に重点を置いて説明します。

Java GSS-APIを有効にしてネイティブGSS-APIを使用する前に、ネイティブGSS-APIとその基礎となる暗号化メカニズムが使用できること、およびユーザー設定で機能することを確認してください。たとえば、ネイティブGSSライブラリが正しい構成で適切なディレクトリにインストールされていること、またKerberosライブラリおよび構成についても同様であることを確認します。ネイティブGSS-APIでは、アプリケーションによって自身のAPIが呼び出される前にメカニズム固有のクレデンシャルを取得し、ネイティブ・メカニズム実装で認識されている場所にそのクレデンシャルが格納済であることを前提としています。したがって、アプリケーションがネイティブGSS-APIをKerberosで使用する場合は、イニシエータ側のkinitツール、アクセプタ側のキータブ・ファイル、またはシステム・ログイン時に取得したデフォルト・クレデンシャルを使用して、適切なネイティブ・クレデンシャル(KerberosチケットやKerberosキーなど)を取得済であることが必要です。

Java GSS-APIでネイティブGSS-APIを使用できるようにするには、次のシステム・プロパティを1つ以上設定し、Javaアプリケーションでこの動作を明示的に有効にする必要があります。

  • sun.security.jgss.native (required): これをtrueに設定すると、Java GSS-APIでネイティブGSSライブラリを使用できるようになります。

  • sun.security.jgss.lib (オプション): ネイティブGSSライブラリのフル・パスに設定します。これが設定されている場合、Java GSS-APIはデフォルトのJavaライブラリ・パスを使用して指定されたライブラリを検索します。これが設定されていない場合、Java GSS-APIはデフォルトのネイティブGSSライブラリを使用します。Windowsでは、これはJDKに含まれているsspi.dllです。このライブラリはクライアント側のみで、デフォルトのクレデンシャルを使用します。他のプラットフォームでは、Java GSS-APIは既存の既知のネイティブGSSライブラリ、たとえばlibgssapi.soまたはlibgssapi_krb5.so (Linuxの場合)、libgssapi_krb5.dylib (macOSの場合)を検索します。

前述のように、ネイティブGSS-APIでは、これらのクレデンシャルがアプリケーションによって取得済で、アクセス可能であることが必要です。Javaアプリケーションは、Java GSS-APIを介してこれらのネイティブ・クレデンシャルにアクセスし、それをピア間でのGSS-APIセキュリティ・コンテキストの確立に使用できます。次に示すように、Subjectが存在する場合、

javax.security.auth.Subject.getSubject(AccessController.getContext()) != null

Java GSS-APIでは、クレデンシャルが現在のSubjectの非公開または公開クレデンシャル・セットから取得されること、また必要なクレデンシャルが見つからない場合はJava GSS-APIコールが必ず失敗することが要求されます。したがって、Subject.doAs/doAsPrivileged(...)コール内でJava GSS-APIコールを実行するJavaプラットフォーム・アプリケーションは、ネイティブ・クレデンシャルをカプセル化する適切なJava GSSCredentialオブジェクトを使用してSubjectのクレデンシャル・セットを生成するか、明示的にシステム・プロパティjavax.security.auth.useSubjectCredsOnlyをfalseに設定して、Java GSS-APIがSubjectのクレデンシャル・セットに加えて、他の場所(ネイティブ・クレデンシャル・キャッシュなど)からもクレデンシャルを取得できるようにする必要があります。

他の代理でGSS-APIセキュリティ・コンテキストの確立を委譲された場合、JavaアプリケーションはGSSContext.getDelegCred()によって返される委譲されたクレデンシャルをJava GSS-APIコール内で明示的に指定するか、この委譲されたクレデンシャルを使用してSubjectオブジェクトを作成し、Subject.doAs/doAsPrivileged(...)コール内でJava GSS-APIコールを実行できます。

ネイティブGSS-APIが有効になると、Simple Authentication and Security Layer (SASL) (「Java SASL APIプログラミングおよび配備ガイド」を参照)などのメカニズムやプロトコルを使用してJava GSS-APIを間接的に呼び出すJavaプラットフォーム・アプリケーションでもユーザーのネイティブ設定およびクレデンシャルが使用されます。

次に示すサンプル・コードでは、Java GSS-APIを使用してGSS-APIセキュリティ・コンテキストを確立し、3者間でデータをセキュアに交換する方法を示します。ここでは、SampleClientFooServerに接続し、FooServerがSampleClientの代理としてFooServer2に接続します。ノート:

  • サンプル・コードはネイティブGSS-APIを有効にして呼び出す必要があります。プリンシパル名のhost@foo.sample.comおよびhost@foo2.sample.comはプレースホルダーであるため、Kerberosデータベース内の実際のプリンシパル名で置き換えてください。

  • セキュリティ・マネージャがインストールされている場合、一部のJava GSS-APIコールにはアクセス権の付与が必要です。詳細は、次のクラスのJavaドキュメントを参照してください。

  • コード例を簡略化するため、ピア間のトークン交換は2つの疑似メソッド(SEND_TOKEN(byte[])およびREAD_TOKEN())で表しています。実際の実装はアプリケーション固有であるため、ここでは表示されません。

  • コードの重複を減らすため、SampleClientFooServerおよびFooServer2のコード・セグメントでは、コンテキスト確立のコードを疑似メソッドESTABLISH_CONTEXT(GSSContext)で参照しています。

次に、Java GSS-APIを使用したESTABLISH_CONTEXT(GSSContext)の実装を示します。

/** 
 * ESTABLISH_CONTEXT(GSSContext ctxt): establishes a context
 * with data confidentiality and mutual authentication.
 */
ctxt.requestConf(true);
ctxt.requestMutualAuth(true);

byte[] inToken = new byte[0];
byte[] outToken = null;

if (ctxt.isInitiator()) {
    while (!ctxt.isEstablished()) {
        // Note: initSecContext(...) always ignores the arguments
        // for the first call because there is no incoming token.
        outToken = ctxt.initSecContext(inToken, 0, inToken.length);

        // Send the output token if generated.
        if (outToken != null) SEND_TOKEN(outToken); // to acceptor

        // Check whether more incoming tokens are expected.
        if (!ctxt.isEstablished()) {
            inToken = READ_TOKEN(); // from acceptor
        }
    }
} else {
    while (!ctxt.isEstablished()) {
        inToken = READ_TOKEN(); // from initiator
        outToken = 
            ctxt.acceptSecContext(inToken, 0, inToken.length);

        // Send the output token if generated.
        if (outToken != null) SEND_TOKEN(outToken); // to initiator
    }
}

次に、SampleClientFooServerおよびFooServer2のコード・セグメントを示します。

SampleClient: FooServerに接続し、サーバーに代理実行を委譲します。すべて問題なく実行されると、FooServer2によって生成された個人用のhelloメッセージが返されます。

GSSManager gssMgr = GSSManager.getInstance();
GSSName serverName = gssMgr.createName(
    "host@foo.sample.com", GSSName.NT_HOSTBASED_SERVICE);
GSSContext context = gssMgr.createContext(
    serverName, null /* default mechanism, which is Kerberos*/,
    null /* default initiator cred */, 
    GSSContext.DEFAULT_LIFETIME);
context.requestCredDelegState(true);

ESTABLISH_CONTEXT(context);

// Make sure credential delegation is available. 
if (!context.getCredDeleg()) {
    context.dispose();
    throw new Exception("credential delegation is denied");
}

byte[] token = READ_TOKEN(); // from "FooServer"
byte[] data = 
   context.unwrap(token, 0, token.length, new MessageProp(true));
context.dispose();

// Should print "Hello from FooServer2 to <client name>" where 
// <client name> is the name of the default initiator.
System.out.println(new String(data));

FooServer: FooServer2SampleClientとして接続し、受信した応答をSampleClientに転送します。

GSSManager gssMgr = GSSManager.getInstance();
GSSName myName = gssMgr.createName(
    "host@foo.sample.com", GSSName.NT_HOSTBASED_SERVICE);
GSSCredential myCred = gssMgr.createCredential(
    acceptorName, GSSCredential.INDEFINITE_LIFETIME,
    (Oid[]) null /* default set of mechanisms */, 
    GSSCredential.ACCEPT_ONLY);
GSSContext acontext = gssMgr.createContext(myCred);

ESTABLISH_ACC_CONTEXT(acontext);

GSSCredential delegCred = acontext.getDelegCred();
if (delegCred != null) {
    byte[] data, token;
    // Establish a context on client's behalf using the delegated 
    // credential.
    GSSName serverName = gssMgr.createName(
        "host@foo2.sample.com", GSSName.NT_HOSTBASED_SERVICE); 
    GSSContext icontext = gssMgr.createContext(
        serverName, null /* default mechanism Kerberos */, 
        delegCred /* act on SampleClient's behalf */, 
        GSSContext.DEFAULT_LIFETIME);

    ESTABLISH_CONTEXT(icontext);

    token = READ_TOKEN(); // from "FooServer2"

    MessageProp msgProp = new MessageProp(true);

    // Forward the reply from FooServer2 to SampleClient.
    data = icontext.unwrap(token, 0, token.length, msgProp);
    token = acontext.wrap(data, 0, data.length, msgProp);
    SEND_TOKEN(token); // to "SampleClient"
    icontext.dispose();
}
acontext.dispose();

FooServer2: 常に、確立されたコンテキストのイニシエータの名前に応じた個人用のhelloメッセージを返信します。

GSSManager gssMgr = GSSManager.getInstance();
GSSName myName = gssMgr.createName(
    "host@foo2.sample.com", GSSName.NT_HOSTBASED_SERVICE);
GSSCredential myCred = gssMgr.createCredential(
    myName, GSSCredential.INDEFINITE_LIFETIME,
    (Oid[]) null /* default set of mechanisms */, 
    GSSCredential.ACCEPT_ONLY);
GSSContext context = gssMgr.createContext(myCred);

ESTABLISH_CONTEXT(context);

byte[] data = new String("Hello from FooServer2 to " + 
    context.getSrcName()).getBytes();
byte[] token = 
    context.wrap(data, 0, data.length, new MessageProp(true));

SEND_TOKEN(token); // to "FooServer"

context.dispose();