Accessing Native GSS-API

To help Java platform applications achieve seamless integration with native applications, the JDK enhances the Java GSS-API to use native GSS-API instead of its own implementation of cryptographic mechanisms when configured to do so. When using the native GSS-API and its underlying native cryptographic mechanisms, the native credentials and settings in users' environment will be picked up automatically. This is different from the default case in which the Java GSS-API uses its own implementation of cryptographic mechanisms. When using Kerberos, Java applications have to supply Kerberos configuration information using the designated Kerberos system properties for the Java GSS-API to function. Introduction to JAAS and Java GSS-API Tutorials covers the default case in great detail, so this section will focus on how to enable or configure Java GSS-API to use native GSS-API.

Before you enable Java GSS-API to use native GSS-API, ensure that native GSS-API and its underlying cryptographic mechanism are available and functioning with user settings. For example, ensure that native GSS libraries are installed at the appropriate directories with proper configurations, and the same applies to the Kerberos library and configurations. Note that native GSS-API assumes that before an application calls its APIs, it has already obtained and stored the mechanism-specific credentials in a location that the native mechanism implementation is aware of. Thus, when an application uses native GSS-API with Kerberos, it must already have obtained the appropriate native credentials, such as Kerberos tickets and keys by using the kinit tool on the initiator side or a keytab file on the acceptor side.

To make the Java GSS-API use native GSS-API, Java applications must explicitly enable this behavior by setting one or more of the following system properties:

  • sun.security.jgss.native (required): Set this to true to enable the Java GSS-API to use native GSS-API.

  • sun.security.jgss.lib (optional, but required for Windows): Set to the full path of the native GSS library. If this is not set, then the Java GSS-API will look for the native GSS library using the default Java library path. If this is not set, then the Java GSS-API will try to search for some well-known native GSS libraries, for example, libgss.so on Solaris, libgssapi.so or libgssapi_krb5.so on Linux, and libgssapi_krb5.dylib on macOS. There is no well-known native GSS-API library on Windows, and you must specify its full path name with this system property.

As mentioned previously, native GSS-API requires that the application had obtained these credentials and that they are accessible. Java applications can access these native credentials through the Java GSS-API and use them for establishing GSS-API security contexts with peers. Note that when a Subject is present, for example,

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

then the Java GSS-API mandates that the credentials be obtained from the private or public credential sets of the current Subject and that the Java GSS-API call must fail if the desired credential cannot be found. Thus, Java platform applications that execute the Java GSS-API calls inside a Subject.doAs/doAsPrivileged(...) call should either populate the Subject's credential sets with the appropriate Java GSSCredential objects that encapsulate the native credentials or explicitly set the system property javax.security.auth.useSubjectCredsOnly to false so that the Java GSS-API can obtain credentials from other locations, for example, from native credential caches, in addition to the Subject's credential sets.

When delegated to establish a GSS-API security context on behalf of others, Java applications can either specify the delegated credential, as returned by GSSContext.getDelegCred(), explicitly in Java GSS-API calls, or create a Subject object with this delegated credential and execute the Java GSS-API calls inside the Subject.doAs/doAsPrivileged(...) calls.

Once the native GSS-API is enabled, Java platform applications that indirectly call Java GSS-API through mechanisms or protocols such as Simple Authentication and Security Layer (SASL) (see Java SASL API Programming and Deployment Guide) will also use user's native settings and credentials.

Here is some sample code that helps demonstrate how to use Java GSS-API to establish GSS-API security contexts and securely exchange data between three parties: SampleClient contacts FooServer, which in turn contacts FooServer2 on behalf of SampleClient. Note:

  • The sample code should be invoked with native GSS-API enabled. The Principal names host@foo.sample.com and host@foo2.sample.com are placeholders and should be replaced with actual principal names in your Kerberos database.

  • When a security manager is installed, some Java GSS-API calls require that permissions be granted. Check the Java documentation of the following classes for more details:

  • To simplify the example, token exchanges between peers are represented by two pseudo-methods: SEND_TOKEN(byte[]) and READ_TOKEN(). Their actual implementation are application-specific and thus not shown here.

  • To reduce code duplication, context establishment code is referred by a pseudo-method, ESTABLISH_CONTEXT(GSSContext), in the code segments for SampleClient, FooServer, and FooServer2.

The following is the implementation for ESTABLISH_CONTEXT(GSSContext) using Java GSS-API.

/** 
 * 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
    }
}

Following are the code segments for SampleClient, FooServer, and FooServer2:

SampleClient: It contacts FooServer and delegates the server to act on its behalf. If all goes well, it should get back a personalized hello message produced by FooServer2.

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: It contacts FooServer2 as SampleClient and forwards the received reply to 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: It always replies with a hello message personalized to the name of the initiator of the established context.

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();