2 Kerberos Module

Chapter Index

  1. General Description
  2. Usage
    1. Getting Credentials
    2. How Authentication Works
    3. Passing Credentials Over
    4. Exchanging Messages
  3. Configuration
    1. Keytab
  4. Testing & Examples
    1. Server
    2. Client

In this chapter, system properties of JVM are shown in curly brackets: {user.name}; other variables are in angle brackets: <uid>.

2.1 General Description

Traditionally, people at Fermilab use various types of authentication programs that store cached tickets differently. The generic implementation of Kerberos in J2SE SDK is missing a functionality that could examine several possible locations in series, in order to find a single valid ticket. Memory caches are not supported, too. Finding a solution of these issues was the initial goal for the project. Later, some additional functions have been implemented for user convenience. At this time, Kerberos Module can:

The password authentication is not supported in Kerberos Module for a pupose. This is done in order to avoid compromising of the password when it is accidentally entered in X terminal.

Note: As soon as password authentication is not provided, the principal should either obtain the ticket with some program and have it in a cache, or the principal's key should be stored in a keytab.

The code is divided into the client, server, and common parts. They are located in the following packages:

List Of Packages
gov.fnal.controls.applets.kerberos Applet for user authentication over HTTP. client
gov.fnal.controls.tools.kerberos High-level configuration and authentication for users. common, client
gov.fnal.controls.tools.kerberos.base Low-level login modules and cache readers. common
gov.fnal.controls.tools.kerberos.realm Tomcat valves for HTTP authentication. server
gov.fnal.controls.tools.kerberos.server High-level classes for services. server

Common and client parts do not have external dependencies.

The server-side code may require:

servlet.jar, catalina.jar
— for Tomcat valves in gov.fnal.controls.tools.kerberos.realm
jconn2.jar, gov.fnal.controls.db.*
— for database logger in gov.fnal.controls.tools.kerberos.server

The code is combined in the following archives:

krb5.jar
General-purpose archive to be used on the client side:
  • gov.fnal.controls.tools.kerberos
  • gov.fnal.controls.tools.kerberos.base
krb5login.jar
Same as above, plus the applet:
  • gov.fnal.controls.applets.kerberos
  • gov.fnal.controls.tools.kerberos
  • gov.fnal.controls.tools.kerberos.base
krb5realm.jar
Server side:
  • gov.fnal.controls.applets.kerberos
  • gov.fnal.controls.tools.kerberos
  • gov.fnal.controls.tools.kerberos.base
  • gov.fnal.controls.tools.kerberos.realm
  • gov.fnal.controls.tools.kerberos.server
  • Database stuff.

2.2 Usage

Let's discuss some common scenario.

On the client side, there sit a human being, who has either the cached ticket or, in few cases, the keytab. This user is running our program and we want to:

  1. Obtain the user's credential;
  2. Get his name;
  3. Send the user's credential to the server side;
  4. Exchange some messages with the server.

On the server, we want to:

  1. Accept and verify a credential of the user that is running the client side;
  2. Get his name;
  3. Exchange some messages with the client.

These basic operations (except message exchange) are implemented in two classes, ClientLoginContext and ServiceLoginContext, that need to be run on client and server sides, correspondingly.

2.2.1 Getting Credentials

On the client side, use:

ClientLoginContext.getInstance().login();

The login method may fail if the routine can not find the valid ticket in caches or obtain it using a keytab. Call logout() when the credential is not needed anymore.

A credential in our case is the ticket-granting ticket (TGT). It contains a big piece of encrypted data intended only for TGS, plus several plain-text attributes, such as the principal name, expiration date, and others. To get the principal name and expiration date directly from ClientLoginContext, use getPrincipalName() and getExpiration() methods. The user principal name always includes a domain, e.g., apetrov@FNAL.GOV (and by the way, this is my principal, not necessarily my e-mail).

It is important to understand, that TGT alone can not work as a proof of identity, because it is no more then some binary data stored in a file or the memory (unless a keytab was used). All its unencrypted attributes, including the principal name, can easily be faked up. Only (TGS) has a secret key to decrypt the ticket body and make sure that everything is correct. This means that the result of getPrincipalName() can be trusted only when the next step (sending/accepting credential) has succeeded.

On the server side, we also have to get a credential for the running process (service) in order to be able to accept the user credential. (This operation was not reflected in the above list, because there was only what we wanted to do). Services can be authenticated only with keytabs. The format of a service principal name is: <service>/<host>@<realm> e.g., host/outland.fnal.gov@FNAL.GOV. Like users, service authentication is started with login method.

2.2.2 How Authentication Works

Authentication on the clients and on the servers works in slightly different ways:

There are few common configurations of Kerberos clients that can be found on most of the computers. On all UNIXes there is kinit program; it keeps tickets in a file. On Mac OS X kinit stores tickets in the memory. On Windows, there is either a MIT suite that includes Leash32 and kinit and stores tickets mostly in the memory, or WRQ Reflection program that uses a file cache.

Note: There is also a Microsoft® verision of Kerberos included in Windows 2000 and Windows XP. In that implementation the tickets are stored in the memory and accessed through Local Security Autority (LSA) API. Kerberos Module does not use LSA tickets, but it looks like the JDK can read them.

For a given operating system, Kerberos Module tries to find the valid ticket in several possible locations:

Supported Ticket Caches
Operating System # Location
Free BSD 1 $KRB5CCNAME1
2 FILE:/tmp/krb5cc_<uid>
Linux 1 $KRB5CCNAME
2 FILE:/tmp/krb5cc_<uid>
Mac OS X 1 API:2
Sun OS 1 $KRB5CCNAME
2 FILE:/tmp/krb5cc_<uid>
Windows 1 API:3
2 %KRB5CCNAME%
3 FILE:{java.io.tmpdir}\krb5cc
4 FILE:{user.home}\My Documents\Reflection\{user.name}@FNAL.GOV.cch
5 FILE:{user.home}\My Documents\Reflection\{user.name}.cch

1 The value of KRB5CCNAME environment variable must include either API: or FILE: prefix.
2 Accessed through native Kerberos support on Mac OS X.
3 Accessed through krbcc32.dll.

2.2.3 Passing Credentials Over

The user credential can be sent to another machine by means of GSS-API. As a result, two peers create a common security context. In reality this means that both the client and the server obtain instances of org.ietf.jgss.GSSContext class. This class allows a program to use some security services for data exchange. In the simpliest case, possession of an instance of this class can be used as a proof of the user identity. The user name can be obtained through getSrcName().toString() call. If two nodes need to establish some sort of an encrypted or reliable communication, GSSContext provides several per-message security services.

An initiative to create GSSContext always comes from the client side. ClientLoginContext class has several request methods that need to be used for this. In ServiceLoginContext, the accept method is used to create a server-side instance of the context. In order to perform request and accept operations, both parties have to be logged in, but this is not required explicitly—login is automatically called whenever the principal is not authenticated or its lifetime has expired. In order to negotiate the context, two peers need to exchange some data, also known as tokens. The core implementation of request and accept methods uses java.nio.channels.Channel classes as transport level abstractions; in a concrete program these classes can be easily linked through a socket, or something like this.

The request method is also used to specify a particular set of security services, such as:

If either confidentiality or integrity is requested, the authentication has to be mutual,—two peers need to show each other their credentials in order to obtain some common encryption key. To complete the mutual authentication, they need to exchange tokens several times. In practice, there are three transactions. Otherwise, if mutual authentication is not required, the requestor needs to send only a single token to the accepting host.

2.2.4 Exchanging Messages

GSSContext has an important function of wrapping and unwrapping data that can be used in secure data exchange. Wrapping means either encryption or adding a Message Integrity Code (MIC). While encryption is to conceal the data, MIC works as a digital signature that prevents data from being altered. GSSContext's methods are wrap and unwrap.

All security services should be specified at the time when GSSContext is initially requested on the client side, using boolean attributes of the request method.

2.3 Configuration

In order to work properly on the server and client sides, Kerberos Module generally requires no or very little changes in its configuration. If, for some reason, you have to change the default settings, this can done through JVM system properties. New values of the properties should be set before the very first usage of any class included in the module. It is desirable to use command line arguments: -D<property-name>=<property-value>.

General-purpose properties are:

java.security.krb5.realm
Default realm. Default value of the default realm is FNAL.GOV.
java.security.krb5.kdc
KDC address. Default value is i-krb-7.fnal.gov:88.
gov.fnal.controls.tools.kerberos.service
Service name. If service name contains a slash, it must be escaped with the back slash. Default value is daeset\/bd.

2.3.1 Keytab

Keytab is a file that stores the principal's key. It is an equivalent of the password for service principals and, more seldom, for user principals—if, for example, this user needs to have an automated login. For a given principal, the keytab can be created with kadmin program. In order to use keytab authentication in Kerberos Module, you may have to overwrite two properties:

gov.fnal.controls.tools.kerberos.keytab
Keytab location. Default value is {user.home}/krb5.keytab.
gov.fnal.controls.tools.kerberos.principal
Principal name. Defaule values are:
  • On the client: {user.name}@<realm>; e.g., apetrov@FNAL.GOV;
  • On the server: <service>/<host>@<realm>, e.g., host/outland.fnal.gov@FNAL.GOV.

2.4 Testing & Examples

In order to make sure that Kerberos Module works properly in a particular configuration, principal are registeres, keytabs available and so on, one can use both ClientLoginContext and ServiceLoginContext as standalone test programs, launching them from the command line. In this mode, they are performing two functions:

  1. Getting and dumping the local credential;
  2. Authenticating the client on the server through GSS-API.

In both cases, -h option will show you the list of possible commands. The source code of these classes can also be used as an example.

2.4.1 Server

Note: You need to make sure that an appropriate service principal is registered in the Kerberos database and you can read its keytab file.

Suppose your keytab file is /tmp/my_keytab. In order to figure out whether the server is configured properly, use -a option (“authenticate only”):

bash-2.05$ java -cp /usr/local/dae/jars/split/krb5realm.jar
-Dgov.fnal.controls.tools.kerberos.keytab=/tmp/my_keytab
gov.fnal.controls.tools.kerberos.server.ServiceLoginContext -a
Settings:
    Current version: 3.7.4
    java.security.auth.login.config=jar:file:/usr/local/dae/releases/v2_3_0_1714/
jars/split/krb5realm.jar!/gov/fnal/controls/tools/kerberos/login.ini
    java.security.krb5.realm=FNAL.GOV
    java.security.krb5.kdc=i-krb-7.fnal.gov:88
    gov.fnal.controls.tools.kerberos.keytab=/tmp/my_keytab
    gov.fnal.controls.tools.kerberos.principal=daeset/bd/foo.fnal.gov@FNAL.GOV
Starting authentication
Nov 22, 2004 3:29:07 PM gov.fnal.controls.tools.kerberos.AbstractLoginContext login
INFO: Kerberos login started
Nov 22, 2004 3:29:08 PM gov.fnal.controls.tools.kerberos.base.KrbKeytabLoginModule login
INFO: Using keytab /tmp/my_keytab
Nov 22, 2004 3:29:08 PM gov.fnal.controls.tools.kerberos.AbstractLoginContext login
INFO: Authenticated as daeset/bd/foo.fnal.gov@FNAL.GOV till null
Service authenticated (299ms)
Finished

Now let's start the server in a mode, when it is listening to an IP port and acceping incoming GSS requests (no command line arguments in this case):

bash-2.05$ java -cp /usr/local/dae/jars/split/krb5realm.jar
-Dgov.fnal.controls.tools.kerberos.keytab=/tmp/my_keytab
gov.fnal.controls.tools.kerberos.server.ServiceLoginContext
Settings:
    Current version: 3.7.4
    java.security.auth.login.config=jar:file:/usr/local/dae/releases/v2_3_0_1714/
jars/split/krb5realm.jar!/gov/fnal/controls/tools/kerberos/login.ini
    java.security.krb5.realm=FNAL.GOV
    java.security.krb5.kdc=i-krb-7.fnal.gov:88
    gov.fnal.controls.tools.kerberos.keytab=/tmp/my_keytab
    gov.fnal.controls.tools.kerberos.principal=daeset/bd/foo.fnal.gov@FNAL.GOV
Opening channel on port 4747
Waiting for new client request...

2.4.2 Client

On the client side, we do not need to have keytabs, but ought to have the valid ticket in a cache. If you have not done this yet:

[apetrov@xxx apetrov]$ kinit
Password for apetrov@FNAL.GOV:

After that, we are going to use Kerberos Module to get our local credential and present it to the server foo.fnal.gov (for mutual authentication add +s and/or +i options):

[apetrov@xxx apetrov]$ java -cp /usr/local/dae/jars/split/krb5.jar
gov.fnal.controls.tools.kerberos.ClientLoginContext -g foo.fnal.gov
Settings:
    Current version: 3.7.4
    java.security.auth.login.config=jar:file:/usr/local/dae/jars/split/krb5.jar!
/gov/fnal/controls/tools/kerberos/login.ini
    java.security.krb5.realm=FNAL.GOV
    java.security.krb5.kdc=i-krb-7.fnal.gov:88
    gov.fnal.controls.tools.kerberos.keytab=/home/apetrov/krb5.keytab
    gov.fnal.controls.tools.kerberos.principal=apetrov@FNAL.GOV
Starting authentication
Nov 22, 2004 4:01:31 PM gov.fnal.controls.tools.kerberos.AbstractLoginContext login
INFO: Kerberos login started
Nov 22, 2004 4:01:31 PM gov.fnal.controls.tools.kerberos.base.KrbCacheReader readTicket
INFO: Using ticket cache FILE:/tmp/bar
Nov 22, 2004 4:01:31 PM gov.fnal.controls.tools.kerberos.base.KrbCacheReader readTicket
INFO: Ticket obtained
Nov 22, 2004 4:01:31 PM gov.fnal.controls.tools.kerberos.AbstractLoginContext login
INFO: Authenticated as apetrov@FNAL.GOV till Tue Nov 23 11:01:24 CST 2004
Client authenticated (199ms)
Login options:
    integrity=        false
    confidentiality=  false
    replayDetection=  false
    sequenceDetection=false
Connecting to foo.fnal.gov:4747
Starting GSS transaction with daeset\/bd@foo.fnal.gov
User authenticated! (663ms)
    established=true
    protReady=true
    srcName=apetrov@FNAL.GOV
    targName=daeset\/bd@foo.fnal.gov
    mutualAuthState=false
    integState=false
    confState=false
    replayDetState=false
    sequenceDetState=false
    lifetime=2147483647
Finished

At the same time, on the server side we will see something like:

Request received from /131.225.XXX.XXX:33989
Starting service authentication...
Nov 22, 2004 4:01:32 PM gov.fnal.controls.tools.kerberos.AbstractLoginContext login
INFO: Kerberos login started
Nov 22, 2004 4:01:32 PM gov.fnal.controls.tools.kerberos.base.KrbKeytabLoginModule login
INFO: Using keytab /tmp/my_keytab
Nov 22, 2004 4:01:32 PM gov.fnal.controls.tools.kerberos.AbstractLoginContext login
INFO: Authenticated as daeset/bd/foo.fnal.gov@FNAL.GOV till null
Service authenticated (290ms)
Starting GSS transaction
User authenticated! (2532ms)
    established=true
    protReady=true
    srcName=apetrov@FNAL.GOV
    targName=daeset\/bd@foo.fnal.gov
    mutualAuthState=false
    integState=false
    confState=false
    replayDetState=false
    sequenceDetState=false
    lifetime=2147483647
Waiting for new client request...