Kerberos, RFC 4120





See notes from O’Reilly book...

See krb5 interfaces here.


Introduction...

Much of this information is paraphrased from the excellent article by Brian Tung entitled The Moron’s Guide to Kerberos, Version 2.0.

A Microsoft webcast slide presentation on Kerberos is available using Internet Explorer (only). It is not well presented, the woman does little else than read the slides, but there are illustrations and lists of information useful especially if you are a new-comer to Kerberos.

A more comprehensive article on Kerberos than this one can be found in Wikipedia.

A really great set of diagrams on how Kerberos works is available from ComputerWorld magazine: http://www.computerworld.com/computerworld/records/images/pdf/kerberos_chart.pdf

An excellent book entitled, Windows Server 2003 Security Infrastructures, written by Jan De Clercq, contains a chapter devoted to Kerberos which became available on-line and so I’ve included it here.

From Microsoft TechNet, good nighttime reading for a good overview of Kerberos and how Windows makes use of it: Kerberos Authentication Technical Reference

It is imperative in reading this page that the terms in bold italics be well assimilated for they are used in all subsequent explanations. These terms are...

Rather than muddy things here, I define these terms in this page as they are used. I continue to italicize some of the more important terms throughout the page. Well known terms such as user, service and password will not receive this treatment. I have mostly disdained the use of acronyms since the purpose of this document is pedagogical. I have also mostly resisted including non-Kerberos terms in the list above to avoid confusion. These will, when appropriate, be presented and italicized as they arise.


How a user is authenticated to a service...

Both the user and the service must trust a Kerberos authentication server (AS), independent of them both. This server may run on any host on the network including the local one.

Both the user and the service must have a shared secret key registered with the AS; such keys are typically called long-term keys, since they last for weeks or months (configurable, I believe).

This illustration was ripped off from Microsoft and the steps it lists follow only loosely what is being said here since it is oriented more toward the actual packet exchange during the process. Three basic steps involved in authenticating a user to an end service...


Back-certification of the server...

...to the user is accomplished too where desired by the service taking the timestamp from the authenticator, adding its own name to it, encrypting it with the session key, then returning it to the user who makes of it whatever he wants.


Why tickets?

An inconvenience in using a password is the frequency or repetition of having to type it in. Users tend then to make use of the same password for different services and also make it easy to type in and therefore easier to guess.

Introducing a new service that solves this problem, the ticket-granting server (TGS). Logically distinct from the authentication server (AS), they may reside on the same host and be known collectively as the key-distribution center (KDC).

The purpose of the ticket-granting server is to add an extra layer of indirection so that the user only needs to enter a password once and the ticket and session key, the authenticating credentials, obtained from that password grant henceforth all further tickets. How this works follows...

The user requests from the authentication server a ticket to talk to the ticket-granting server, the ticket-granting ticket (TGT) or initial ticket. The session key is encrypted using the user’s long-term key, so the password is needed to decrypt it from the AS response to the user.

Once receiving the ticket-granting ticket, whenever the user wishes to connect to a service, he requests the ticket to do so not from the authentication server, but from the ticket-granting server. The reply isn’t encrypted with the user’s secret key, but with the session key that came with the ticket-granting ticket so the user’s password is not needed to obtain the new session key that will be used with the end service.

Brian Tung’s metaphor for this is compelling...

Imagine visiting a secure place of business. You show your personal identification (driver’s license, passport, etc.) to the security receptionist and are issued a special ID for use at the facility. When you enter certain areas, you don’t show your personal identification all over again, this would open your personal identification to loss or theft. Instead, you show your temporary facility ID. If that ID were dropped or stolen, it could easily be invalidated and a new one issued to replace it, a process far simpler than replacing your personal identification.


From the user’s perspective: how Kerberos is used...

To begin to use Kerberos, a Kerberos principal must be established. This is something like a simple account on any host. The name of the principal usually appears as name@realm, in my case, [email protected]. With the principal are associated the name, password and other information which are stored in the Kerberos database and encrypted using a Kerberos master key to protect it from being examined by anyone.

Some services, for example, rlogin, but also Active Directory and, hence, Vintela Authentication Services (VAS), require Kerberos authentication. However, this discussion goes beyond what is necessary for VAS since after installation and the initial join (via the vastool join command), this is all set up.

To use such a service, a ticket-granting ticket is needed. This is obtained via the command (I’m using one of my usernames):

	% kinit
	Password for [email protected]:

You enter a password and kinit submits a request to the authentication server for a ticket-granting ticket. If this is successful, a command will confirm this for you...

	% klist
	Ticket cache: FILE:/tmp/krb5cc_1015
	Default principal: [email protected]

	Valid starting         Expires         Service principal
	03/29/06 13:53:48  03/29/06 23:53:48   krbtgt/[email protected]

Other tickets you have are listed as well. Until the ticket expires, if you then use rlogin, for example, it will happen automatically (without the need to type in a password):

	rlogin newhost.domain
	Last login: Tue Mar 28 17:16:43 from...

Then, returning to klist you’ll see an additional line like:

	% klist
	Ticket cache: FILE:/tmp/krb5cc_1015
	Default principal: [email protected]

	Valid starting         Expires         Service principal
	03/29/06 13:53:48  03/29/06 23:53:48   krbtgt/[email protected]
	03/29/06 13:53:48  03/29/06 23:53:48   host/[email protected]


Renewable tickets...

An application may run for such a long time that it has to reauthenticate several times over that period. Keeping a long-lived ticket exposes credential to theft, so Kerberos creates renewable tickets.


Proxy tickets...

A client (user) may wish a service to act on its behalf; it acquires tickets as if it were the client. Network addresses are added to the ticket to protect it.


Cross realm authentication...

As a network grows, along with it the number of requests for services too creating bottlenecks of the authentication and ticket-granting services. The system isn’t scalable. To solve this, the notion of realm exists in Kerberos. Each realm has its own AS and TGS.

When even the number of realms grows large, it becomes necessary to inter-refer between them via remote ticket-granting servers (RTGS) and even to transit between numerous realms to find the service. All the names of transited realms are recorded in the ticket which adds to what the end service can use to judge whether to accept the authentication. New to Kerberos 5.


Locating a key-distribution center (KDC)...

There is, in the example of Microsoft Windows servers, a KDC running on every domain controller. The Kerberos client queries for a domain controller, queries the domain name service to find a domain controller which will tell it where the nearest KDC is found.


PAC...

The privilege-attribute certificate (PAC) is used strictly in Windows 2000 Kerberos authentication and contains such information as the user’s security identity (SID), group membership SIDs and user’s rights on the domain.


Kerberized applications

Kerberized applications generally deal a lot more with the Kerberos (krb5) API in that they do ticket requests, handle forwarding of tickets, allow you even to renew and reget tickets, etc. The Kerberos protocol is wider in scope than just an authentication mechanism like pluggable authentication modules (PAM), which can itself make use of Kerberos, but if authentication is all you’re after, PAM is your world.

There are special versions of applications such as ftp, rcp, rsh and telnet that are Kerberized.


Kerberos exportable!

A version named Bones does no encryption of the exchanged messages and is, therefore, freely exportable from the United States. Bones was originally created for exporting Kerberos from MIT to a university in Australia.

Later, someone put encryption back into Bones, named it eBones. A version from this was created in Sweden (by then, Kerberos 5) and called Heimdal after the watchman of Nordic mythology (Kerberos is the name of the three-headed dog that guards the gates of Hades in Greek mythology).

Heimdal Kerberos is subject to no United States government export restrictions although a product using it cannot simply be exported as is, but must be slightly dissassmbled for exporting and reassembled afterward.


By the way, Kerberos differs from RSA in that...

...it doesn’t have both a public and a private key. It uses conventional or symmetric cryptography (Microsoft Active Directory) while RSA (Novell eDirectory) uses public-key cryptography.


Some terminological comparison...

Not necessarily relevant to the greater page, but I have to put the notes somewhere...

What Kerberos calls a realm usually maps to an Active Directory domain, not because they are the same thing necessarily, but because for implementation, it is a natural alignment.

VAS code mostly treats realm and domain as indistinguishable. Where a variable is named one, it may really be the other and vice versa.

Microsoft Active Directory uses the term forest to refer to what Novell eDirectory calls a tree.

asymmetric keys in Kerberos are only used (and a phenomenon of) smart card technology and support.


Ah, VAS!

Here’s the blow-by-blow of how VAS uses PAM and Kerberos to log into a *nix box...

[This is extracted from a PAM-oriented statement by John Bowers.]

ssh, being a PAM-enabled application, goes through the standard PAM authentication process:

pam_authenticate
pam_acct_mgmt
pam_set_cred
pam_open_session

After those steps, the user now has a shell and does stuff for a while, then...
...user decides he or she has finished and logs out.

All of these things hook into the VAS PAM module; here is the Readers’ Digest version of how this works:

pam_authenticate

All communication with the key-distribution center (KDC) happens in this step.

1. Request a ticket granting ticket (TGT) for the authenticating principal.
2. Using the TGT, request a service ticket for the machine being logged into.
3. Validate the service ticket.

pam_acct_mgmt

Validates the user has a valid account. This includes many things like verifying the account isn’t expired or disabled, making sure that the password isn’t expires, checking logon hour restrictions. If it is discovered that the password is expired, that information is returned here and ssh will additionally call pam_ch_authok to change the user’s password.

pam_set_cred

Stores the TGT and service ticket recieved during pam_authenticate and placed into the user’s own credential cache. This is usually stored in the /tmp filesystem under a name like /tmp/krbcc_<user’s UID>.

pam_open_session

Creates user’s home directory if it doesn’t yet exist on the host. Does anything else needed to prepare for actually giving the user a shell.

Once user has a shell and works for a while, he or she logs out.


Kerberos components and their geography...

Here’s an interesting illustration I ripped off from Microsoft that shows where the different Kerberos components reside (are used). The components by the same name are the same components between each image. This means that the “User Key” in the Workstation and the Domain Controller are the same and this component is neither used nor seen by the Server.


The Quinn Vallance explanation of VAS with Kerberos...

[Note: I’m still cogitating this metaphorical explanation to determine if it really adds anything or not. It may not survive my cogitation. In the meantime, I’m amending it considerably without leaving any indication of what was the content of the original quote.]

- With Kerberos, everything is either a user or a service.
- Think of the VAS-installed host as a Kerberized service, e.g.: host/instance@REALM.
- VAS facilitates the Kerberos exchange on the user’s behalf. (This is not to be thought of or called “a proxy.”)
- Thus, when a user first attempts to log in (or, in our metaphor here, “gain access to the host service”), the VAS PAM requests a TGT from the ticket granting server (TGS) on the user’s behalf.
- Once the user enters his password, and decrypts the ticket granting ticket (TGT), he can use that TGT and the included session key, as a method to request access to other Kerberized services from the TGS, without having to enter his password again.


Some Simo Sorce wisdom...

Kerberos is engineered to never send passwords on the wire. The [Microsoft implementation] of Kerberos uses “pre-auth.” This means that the request for a Kerberos ticket has the time field encrypted with the user’s password. So if that’s wrong you are not even given a ticket and a failure is reported.


Notes from the O’Reilly book...

The Three As of Kerberos

There are three As of Kerberos:

 
Authentication, a process of verifying the identity of a particular user by asking for information that proves identity. Identity is is one (or a combination) of three categories or factors:

  • What user knows—a password

  • What user has—a security token, smartcard, etc.

  • Who user is—a recognizable biological factor (thumbprint, retina scan, etc.)

Authorization, granting (or denying) access to specific resources based on identity and, therefore, occurs after authentication. Authorization is not responsible for failed or faked authentication.

Auditing takes results from authentication and authorization and records them into an audit log. Authentication and authorization are preventative whereas auditing is reactive—helping to improve security in post-violation.

Additional Kerberos Concepts

 
Privacy is accomplished by way of encryption, a process of converting a message (data, text, etc.) into gibberish via DES (and triple DES, later AES) or RC4 (/arcfour/, a Microsoft algorithm).

Message integrity, proof the message has not been tampered with in transit. The message integrity algorithm is referred to as a hash.

Hash, a mathematical, one-way function that takes an arbitrarily long message or piece of data and produces a fixed-length (64-256 bit) messages that represents the original one. This is one-way because while it’s easy for the algorithm to produce the hash, it’s difficult or impossible to reverse the hash into the original message.


What is Smartcard?

A smartcard is basically a store for a certificate and a private key.


krb5 Library Interface Documentation

This is quickly hacked together because I can’t find any out there on the web.


IROMVA...

This acronym is short for incremental retrieval of multivalued attributes.


krb5_config_get_int...
krb5_config_get_int( krb5ctx, NULL, "bungo", "debug-level", NULL )

yields the integer (value) associated with this setting in krb5.conf:

        [bungo]
          debug-level = 5
krb5_config_get_int_default...
krb5_config_get_int_default( krb5ctx, NULL, 0, "bungo", "debug-level", NULL )

yields the integer (value) associated with this setting in krb5.conf or the default value supplied if unset:

        [bungo]

If "debug-level" is missing from this section in krb5.conf, 0 will be returned.


krb5_config_get_bool_default...
krb5_config_get_bool_default( krb5ctx, NULL, FALSE, "bungo", "workstation-mode", NULL )

yields TRUE or FALSE as established by the setting; if missing, then the default value passed in.

        [bungo]
          workstation-mode = true

krb5_config_get_time_default...
krb5_config_get_time_default( krb5ctx, NULL, (3600 * 8), "pkinit", "trusted-certs-update-interval", NULL )

yields an expected value of second as established by the setting; if missing, then the default value passed in (as shown).

Probably not much difference from krb5_config_get_int_default.


krb5_config_get_string...
krb5_config_get_string( krb5ctx, NULL, "bungo", "ignore-path", NULL )

yields the string (value) associated with this setting in krb5.conf:

    [bungo]
      ignore_path = /etc/bungo-paths.dat

The string pointer returned is to a data structure deep inside the Kerberos library created to represent the entire krb5.conf file. So , the resulting pointer need not be freed as it wasn't allocated as a result of this call.

krb5_config_get_string_default...
krb5_config_get_string_default( krb5ctx, NULL, "poopsie", "section", "piddle-dee", NULL )

yields the string (value) associated with this setting in krb5.conf or the default value supplied if unset:

    [section]
      piddle-dee = tech

If "piddle-dee" were missing in this section, then "poopsie" would be returned. The string pointer returned is to a data structure deep inside the Kerberos library created to represent the entire krb5.conf file. So , the resulting pointer need not be freed as it wasn't allocated as a result of this call.


krb5_config_get_list...
krb5_config_get_list( kctx, NULL, "ypd", "nismaps", NULL );

yields a pointer to a list of string bindings from a stanza that must be traversed.

        [ypd]
          nismaps = {
            netgroup = netgroup-jp
            hosts = hosts-jp
          }

This list is traversed thus; the comments show the fields available when running each binding on this list. In this example, the list is essentially being reconstructed as a different linked list. (A few string-manipulation function will have to be imagined here.)

Sample code

	typedef struct
	{
	    char    *nistypename;
	    char    *mapname;   /* this map's name in LDAP cn/nisMapName */
	    char    *searchbase;/* (optional) */
	} admin_yp_nismap_spec_t;

	...
	{
	   const krb5_config_binding    *b = NULL, *runner = NULL;

	    for( runner = b; runner; runner = runner->next )
	    {
	        char    *p;

	        if( runner->name )
	            nismap_count++;

	//      printf( "type      = %s", ( runner->type == krb5_config_string )
	//                                              ? "krb5_config_string"
	//                                              : "krb5_config_list" );
	//      printf( "name      = %s", runner->name );
	//      printf( "next      = %p", runner->next );
	//      printf( "u.string  = %s", runner->u.string );
	//      printf( "u.list    = %p", runner->u.list );
	//      printf( "u.generic = %p", runner->u.generic );

	        /* allocate the node... */
	        if( !( nismap = calloc( 1, sizeof( admin_yp_nismap_spec_t ) ) ) )
	            goto CLEANUP;

	        if( !runner->u.string || !*runner->u.string )
	        {
	            printf( "%s: NIS map for %s", __FUNCTION__, runner->name );
	            printf( "%s:   missing mapname, ignoring...",
	                          __FUNCTION__ );
	            nismap_count--;
	            continue;
	        }
	        else
	        {
	            printf( "%s: NIS map for %s", __FUNCTION__, runner->name );
	        }

	        /* the NIS typename is there for certain... */
	        if( !( nismap->nistypename = strdup( runner->name ) ) )
	            goto CLEANUP;

	        /*
	         * u.string points at the (entire) right-hand side (after '=').
	         *
	         * Run the right-hand side for a mapname (mandatory or the key doesn't
	         * make any sense, but it could happen if someone puts in a key + =
	         * with no right-hand expression) and optional searchbase in the
	         * presence of which the mapname is followed by a colon (:) and the
	         * searchbase is bracketed between square brackets.
	         *
	         * The krb5_config_- functions chew up any white space at the beginning
	         * of the line before we get it, however, we know from experience that
	         * they don't trim whitespace at the end; so we'll do it because it
	         * often creates nastiness.
	         */
	        p = runner->u.string;

	        /* mandatory mapname (potentially different from the typename)... */
	        if( string_skipToChar_or_end( &p, ':', '\n' ) == -1 || *p == '\n' )
	        {
	            char    *q = nismap->mapname = strdup( runner->u.string );

	            /* (there is no searchbase OU...) */
	            if( q )
	            {
	                string_trimWhitespace( &q );

	                if( strlen( q ) < 1 )
	                {
	                    printf( "%s: expecting map name for %s",
	                                  __FUNCTION__,
	                                  nismap->nistypename );
	                    err = EINVAL;
	                    goto CLEANUP;
	                }
	            }
	        }
	        else if( *p == ':' )    /* mapname followed by searchbase OU... */
	        {
	            char    *q = NULL;

	            /* null-terminate mapname prior to alloction... */
	            *p++ = '\0';

	            if( !( q = nismap->mapname = strdup( runner->u.string ) )
	                || strlen( q ) < 1 )
	            {
	                printf( "%s: expecting map name for %s",
	                            __FUNCTION__,
	                            nismap->nistypename );
	                err = EINVAL;
	                goto CLEANUP;
	            }

	            /* now look for opening bracket of search base... */
	            if( string_skipToChar( &p, '[' ) != -1 )
	            {
	                char    *sb = p+1;

	                /* Skip to the end of the searchbase; this precludes the use of
	                 * square brackets inside a searchbase OU since we're not
	                 * attempting to match nested ones:
	                 * [CN=goofball,DC=acme,DC=com]  --sample legal searchbase
	                 */
	                ( void ) string_skipToChar_or_end( &p, ']', '\n' );

	                /* (no closing bracket is a syntax error */
	                if( *p != ']' )
	                {
	                    printf( "%s: looking for ']' to close "
	                                  "searchbase for %s",
	                                  __FUNCTION__,
	                                  nismap->nistypename );
	                    printf( "%s:  Irrecoverable error (%s), skipping...",
	                                  __FUNCTION__,
	                                  nismap->nistypename );
	                    err = EINVAL;
	                    goto CLEANUP;
	                }

	                /* truncate to just the searchbase */
	                *p = '\0';

	                if( !( nismap->searchbase = strdup( sb ) ) )
	                {
	                    printf( "%s: error allocating memory for "
	                                  "searchbase for %s",
	                                  __FUNCTION__,
	                                  nismap->nistypename );
	                    goto CLEANUP;
	                }
	            }
	        }

	        /* (trailing whitespace already snipped from mapname and searchbase) */
	        printf( "%s:   mapname=%s",
	                      __FUNCTION__,
	                      nismap->mapname );

	        if( nismap->searchbase )
	            printf( "%s:   searchbase=[%s]",
	                          __FUNCTION__,
	                          nismap->searchbase );
	    }
	    ...

krb5_config_get_strings...
krb5_config_get_strings( krb5ctx, NULL, "ypd", "client-addrs", NULL );

yields a pointer to a list of strings.

    [ypd]
      client-addrs = 192.168.0.1, 192.168.0.2

Sample code

	...
	{
	    int             rval = 0, i = 0, j = 0, num_addrs = 0, entry_type = 0;
	    char          **addrs = NULL;
	    char          **extra_addrs = NULL;
	    krb5_context    context = NULL;
	    struct ifaddrs *local_addrs = NULL, *runner = NULL;
	    struct in_addr  addr;
	    struct hostent *hostinfo = NULL;
	
	    extra_addrs = krb5_config_get_strings( context, NULL, "ypd", "client-addrs", NULL );

	    /* count how many addresses to deal with */
	    for( runner = local_addrs; runner; runner = runner->ifa_next )
	    {
	        if( runner->ifa_addr == NULL )
	            continue;

	        if( !( runner->ifa_flags & IFF_UP )
	           || runner->ifa_addr->sa_family != AF_INET )
	        {
	            continue;
	        }

	        num_addrs++;
	    }

	    if( extra_addrs )
	    {
	        for( i = 0; extra_addrs[i]; i++ )
	            num_addrs++;
	    }

	    if( num_addrs )
	    {
	        if( !( addrs = calloc( num_addrs+1, sizeof( char * ) ) ) )
	        {
	            rval = ENOMEM;
	            goto FINISHED;
	        }

	        for( runner = local_addrs, i = 0; runner; runner = runner->ifa_next )
	        {
	            if( runner->ifa_addr == NULL )
	                continue;

	            if( !( runner->ifa_flags & IFF_UP )
	                || runner->ifa_addr->sa_family != AF_INET )
	            {
	                continue;
	            }

	            if( ( addrs[i++] = strdup(
	                inet_ntoa( ( ( struct sockaddr_in * )
	                                     ( runner->ifa_addr ) )->sin_addr ) ) )
	                  == NULL )
	            {
	                rval = ENOMEM;
	                goto FINISHED;
	            }
	            printf( "%s: Added address %s as a valid address.",
	                     __FUNCTION__,
	                     addrs[i-1] );
	        }

	        if( !extra_addrs )
	            goto FINISHED;

	        for( j = 0; extra_addrs[ j ]; j++ )
	        {
	            /* if it's a hostname, convert to IP address */
	            if( ( entry_type = yp_get_entry_type( extra_addrs[ j ] ) )
	                    == YP_ENTRY_HOSTNAME )
	            {
	                /* it's probably a hostname, convert it to an IP address,
	                 * ignore hostnames we can't resolve */
	                if( ( hostinfo = gethostbyname( extra_addrs[ j ] ) ) )
	                {
	                    struct in_addr foo;

	                    memcpy( &foo.s_addr,
	                            hostinfo->h_addr_list[ 0 ],
	                            sizeof( in_addr_t ) );

	                    if( ( addrs[ i++ ] = strdup( inet_ntoa( foo ) ) ) == NULL )
	                    {
	                        rval = ENOMEM;
	                        goto FINISHED;
	                    }
	                }
	                else
	                {
	                    /* couldn't resolve the hostname, so ignore it */
	                }
	            }
	            else if( entry_type != YP_ENTRY_INVALID )
	            {
	                /* it's a valid IP address, range, mask, or wildcard */
	                if( ( addrs[ i++ ] = strdup( extra_addrs[ j ] ) ) == NULL )
	                {
	                    rval = ENOMEM;
	                    goto FINISHED;
	                }
	            }
	        }
	    }
	    ...