 21f7a5f9ad
			
		
	
	21f7a5f9ad
	
	
	
		
			
			git-svn-id: svn://svn.h5l.se/heimdal/trunk/heimdal@16772 ec53bebd-3082-4978-b11e-865c3cabbd6b
		
			
				
	
	
		
			388 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			388 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| @c $Id$
 | |
| 
 | |
| @node Programming with Kerberos, Migration, Windows 2000 compatability, Top
 | |
| @chapter Programming with Kerberos
 | |
| 
 | |
| First you need to know how the Kerberos model works, go read the
 | |
| introduction text (@pxref{What is Kerberos?}).
 | |
| 
 | |
| @menu
 | |
| * Kerberos 5 API Overview::     
 | |
| * Walkthrough of a sample Kerberos 5 client::  
 | |
| * Validating a password in a server application::  
 | |
| * API differences to MIT Kerberos::  
 | |
| @end menu
 | |
| 
 | |
| @node Kerberos 5 API Overview, Walkthrough of a sample Kerberos 5 client, Programming with Kerberos, Programming with Kerberos
 | |
| @section Kerberos 5 API Overview
 | |
| 
 | |
| All functions are documented in manual pages.  This section tries to
 | |
| give an overview of the major components used in Kerberos library, and
 | |
| point to where to look for a specific function.
 | |
| 
 | |
| @subsection Kerberos context
 | |
| 
 | |
| A kerberos context (@code{krb5_context}) holds all per thread state. All global variables that
 | |
| are context specific are stored in this structure, including default
 | |
| encryption types, credential cache (for example, a ticket file), and default realms.
 | |
| 
 | |
| See the manual pages for @manpage{krb5_context,3} and
 | |
| @manpage{krb5_init_context,3}.
 | |
| 
 | |
| @subsection Kerberos authentication context
 | |
| 
 | |
| Kerberos authentication context (@code{krb5_auth_context}) holds all
 | |
| context related to an authenticated connection, in a similar way to the
 | |
| kerberos context that holds the context for the thread or process.
 | |
| 
 | |
| The @code{krb5_auth_context} is used by various functions that are
 | |
| directly related to authentication between the server/client. Example of
 | |
| data that this structure contains are various flags, addresses of client
 | |
| and server, port numbers, keyblocks (and subkeys), sequence numbers,
 | |
| replay cache, and checksum types.
 | |
| 
 | |
| See the manual page for @manpage{krb5_auth_context,3}.
 | |
| 
 | |
| @subsection Kerberos principal
 | |
| 
 | |
| The Kerberos principal is the structure that identifies a user or
 | |
| service in Kerberos. The structure that holds the principal is the
 | |
| @code{krb5_principal}. There are function to extract the realm and
 | |
| elements of the principal, but most applications have no reason to
 | |
| inspect the content of the structure.
 | |
| 
 | |
| The are several ways to create a principal (with different degree of
 | |
| portability), and one way to free it.
 | |
| 
 | |
| See manual page for @manpage{krb5_principal,3} for more information
 | |
| about the functions.
 | |
| 
 | |
| @subsection Credential cache
 | |
| 
 | |
| A credential cache holds the tickets for a user. A given user can have
 | |
| several credential caches, one for each realm where the user have the
 | |
| initial tickets (the first krbtgt).
 | |
| 
 | |
| The credential cache data can be stored internally in different way, each of them for
 | |
| different proposes.  File credential (FILE) caches and processes based
 | |
| (KCM) caches are for permanent storage. While memory caches (MEMORY)
 | |
| are local caches to the local process.
 | |
| 
 | |
| Caches are opened with @manpage{krb5_cc_resolve,3} or created with
 | |
| @manpage{krb5_cc_gen_unique,3}.
 | |
| 
 | |
| If the cache needs to be opened again (using
 | |
| @manpage{krb5_cc_resolve,3}) @manpage{krb5_cc_close,3} will close the
 | |
| handle, but not the remove the cache. @manpage{krb5_cc_destroy,3} will
 | |
| zero out the cache, remove the cache so it can no longer be
 | |
| referenced.
 | |
| 
 | |
| See also manual page for @manpage{krb5_ccache,3}
 | |
| 
 | |
| @subsection Kerberos errors
 | |
| 
 | |
| Kerberos errors are based on the com_err library.  All error codes are
 | |
| 32-bit signed numbers, the first 24 bits define what subsystem the
 | |
| error originates from, and last 8 bits are 255 error codes within the
 | |
| library.  Each error code have fixed string associated with it.  For
 | |
| example, the error-code -1765328383 have the symbolic name
 | |
| KRB5KDC_ERR_NAME_EXP, and associated error string ``Client's entry in
 | |
| database has expired''.
 | |
| 
 | |
| This is a great improvement compared to just getting one of the unix
 | |
| error-codes back.  However, Heimdal have an extention to pass back
 | |
| customised errors messages.  Instead of getting ``Key table entry not
 | |
| found'', the user might back ``failed to find
 | |
| host/host.example.com@@EXAMLE.COM(kvno 3) in keytab /etc/krb5.keytab
 | |
| (des-cbc-crc)''.  This improves the chance that the user find the
 | |
| cause of the error so you should use the customised error message
 | |
| whenever its available.
 | |
| 
 | |
| See also manual page for @manpage{krb5_get_error_string,3} and
 | |
| @manpage{krb5_get_err_text,3}.
 | |
| 
 | |
| @subsection Keytab management
 | |
| 
 | |
| A keytab is a storage for locally stored keys. Heimdal includes keytab
 | |
| support for Kerberos 5 keytabs, Kerberos 4 srvtab, AFS-KeyFile's,
 | |
| and for storing keys in memory.
 | |
| 
 | |
| Keytabs are used for servers and long-running services.
 | |
| 
 | |
| See also manual page for @manpage{krb5_keytab,3}
 | |
| 
 | |
| @subsection Kerberos crypto
 | |
| 
 | |
| See also manual page for @manpage{krb5_crypto_init,3},
 | |
| @manpage{krb5_keyblock,3}, @manpage{krb5_create_checksum,3}, 
 | |
| and @manpage{krb5_encrypt,3}.
 | |
| 
 | |
| @node Walkthrough of a sample Kerberos 5 client, Validating a password in a server application, Kerberos 5 API Overview, Programming with Kerberos
 | |
| @section Walkthrough of a sample Kerberos 5 client
 | |
| 
 | |
| This example contains parts of a sample TCP Kerberos 5 clients, if you
 | |
| want a real working client, please look in @file{appl/test} directory in
 | |
| the Heimdal distribution.
 | |
| 
 | |
| All Kerberos error-codes that are returned from kerberos functions in
 | |
| this program are passed to @code{krb5_err}, that will print a
 | |
| descriptive text of the error code and exit. Graphical programs can
 | |
| convert error-code to a human readable error-string with the
 | |
| @manpage{krb5_get_err_text,3} function.
 | |
| 
 | |
| Note that you should not use any Kerberos function before
 | |
| @code{krb5_init_context()} have completed successfully. That is the
 | |
| reason @code{err()} is used when @code{krb5_init_context()} fails.
 | |
| 
 | |
| First the client needs to call @code{krb5_init_context} to initialise
 | |
| the Kerberos 5 library. This is only needed once per thread
 | |
| in the program. If the function returns a non-zero value it indicates
 | |
| that either the Kerberos implementation is failing or its disabled on
 | |
| this host.
 | |
| 
 | |
| @example
 | |
| #include <krb5.h>
 | |
| 
 | |
| int
 | |
| main(int argc, char **argv)
 | |
| @{
 | |
|         krb5_context context;
 | |
| 
 | |
|         if (krb5_context(&context))
 | |
|                 errx (1, "krb5_context");
 | |
| @end example
 | |
| 
 | |
| Now the client wants to connect to the host at the other end. The
 | |
| preferred way of doing this is using @manpage{getaddrinfo,3} (for
 | |
| operating system that have this function implemented), since getaddrinfo
 | |
| is neutral to the address type and can use any protocol that is available.
 | |
| 
 | |
| @example
 | |
|         struct addrinfo *ai, *a;
 | |
|         struct addrinfo hints;
 | |
|         int error;
 | |
| 
 | |
|         memset (&hints, 0, sizeof(hints));
 | |
|         hints.ai_socktype = SOCK_STREAM;
 | |
|         hints.ai_protocol = IPPROTO_TCP;
 | |
| 
 | |
|         error = getaddrinfo (hostname, "pop3", &hints, &ai);
 | |
|         if (error)
 | |
|                 errx (1, "%s: %s", hostname, gai_strerror(error));
 | |
| 
 | |
|         for (a = ai; a != NULL; a = a->ai_next) @{
 | |
|                 int s;
 | |
| 
 | |
|                 s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
 | |
|                 if (s < 0)
 | |
|                         continue;
 | |
|                 if (connect (s, a->ai_addr, a->ai_addrlen) < 0) @{
 | |
|                         warn ("connect(%s)", hostname);
 | |
|                             close (s);
 | |
|                             continue;
 | |
|                 @}
 | |
|                 freeaddrinfo (ai);
 | |
|                 ai = NULL;
 | |
|         @}
 | |
|         if (ai) @{
 | |
|                     freeaddrinfo (ai);
 | |
|                     errx ("failed to contact %s", hostname);
 | |
|         @}
 | |
| @end example
 | |
| 
 | |
| Before authenticating, an authentication context needs to be
 | |
| created. This context keeps all information for one (to be) authenticated
 | |
| connection (see @manpage{krb5_auth_context,3}).
 | |
| 
 | |
| @example
 | |
|         status = krb5_auth_con_init (context, &auth_context);
 | |
|         if (status)
 | |
|                 krb5_err (context, 1, status, "krb5_auth_con_init");
 | |
| @end example
 | |
| 
 | |
| For setting the address in the authentication there is a help function
 | |
| @code{krb5_auth_con_setaddrs_from_fd} that does everything that is needed
 | |
| when given a connected file descriptor to the socket.
 | |
| 
 | |
| @example
 | |
|         status = krb5_auth_con_setaddrs_from_fd (context,
 | |
|                                                  auth_context,
 | |
|                                                  &sock);
 | |
|         if (status)
 | |
|                 krb5_err (context, 1, status, 
 | |
|                           "krb5_auth_con_setaddrs_from_fd");
 | |
| @end example
 | |
| 
 | |
| The next step is to build a server principal for the service we want
 | |
| to connect to. (See also @manpage{krb5_sname_to_principal,3}.)
 | |
| 
 | |
| @example
 | |
|         status = krb5_sname_to_principal (context,
 | |
|                                           hostname,
 | |
|                                           service,
 | |
|                                           KRB5_NT_SRV_HST,
 | |
|                                           &server);
 | |
|         if (status)
 | |
|                 krb5_err (context, 1, status, "krb5_sname_to_principal");
 | |
| @end example
 | |
| 
 | |
| The client principal is not passed to @manpage{krb5_sendauth,3}
 | |
| function, this causes the @code{krb5_sendauth} function to try to figure it
 | |
| out itself.
 | |
| 
 | |
| The server program is using the function @manpage{krb5_recvauth,3} to
 | |
| receive the Kerberos 5 authenticator.
 | |
| 
 | |
| In this case, mutual authentication will be tried. That means that the server
 | |
| will authenticate to the client. Using mutual authentication
 | |
| is good since it enables the user to verify that they are talking to the
 | |
| right server (a server that knows the key).
 | |
| 
 | |
| If you are using a non-blocking socket you will need to do all work of
 | |
| @code{krb5_sendauth} yourself. Basically you need to send over the
 | |
| authenticator from @manpage{krb5_mk_req,3} and, in case of mutual
 | |
| authentication, verifying the result from the server with
 | |
| @manpage{krb5_rd_rep,3}.
 | |
| 
 | |
| @example
 | |
|         status = krb5_sendauth (context,
 | |
|                                 &auth_context,
 | |
|                                 &sock,
 | |
|                                 VERSION,
 | |
|                                 NULL,
 | |
|                                 server,
 | |
|                                 AP_OPTS_MUTUAL_REQUIRED,
 | |
|                                 NULL,
 | |
|                                 NULL,
 | |
|                                 NULL,
 | |
|                                 NULL,
 | |
|                                 NULL,
 | |
|                                 NULL);
 | |
|         if (status)
 | |
|                 krb5_err (context, 1, status, "krb5_sendauth");
 | |
| @end example
 | |
| 
 | |
| Once authentication has been performed, it is time to send some
 | |
| data. First we create a krb5_data structure, then we sign it with
 | |
| @manpage{krb5_mk_safe,3} using the @code{auth_context} that contains the
 | |
| session-key that was exchanged in the
 | |
| @manpage{krb5_sendauth,3}/@manpage{krb5_recvauth,3} authentication
 | |
| sequence.
 | |
| 
 | |
| @example
 | |
|         data.data   = "hej";
 | |
|         data.length = 3;
 | |
| 
 | |
|         krb5_data_zero (&packet);
 | |
| 
 | |
|         status = krb5_mk_safe (context,
 | |
|                                auth_context,
 | |
|                                &data,
 | |
|                                &packet,
 | |
|                                NULL);
 | |
|         if (status)
 | |
|                 krb5_err (context, 1, status, "krb5_mk_safe");
 | |
| @end example
 | |
| 
 | |
| And send it over the network.
 | |
| 
 | |
| @example
 | |
|         len = packet.length;
 | |
|         net_len = htonl(len);
 | |
| 
 | |
|         if (krb5_net_write (context, &sock, &net_len, 4) != 4)
 | |
|                 err (1, "krb5_net_write");
 | |
|         if (krb5_net_write (context, &sock, packet.data, len) != len)
 | |
|                 err (1, "krb5_net_write");
 | |
| @end example
 | |
| 
 | |
| To send encrypted (and signed) data @manpage{krb5_mk_priv,3} should be
 | |
| used instead. @manpage{krb5_mk_priv,3} works the same way as
 | |
| @manpage{krb5_mk_safe,3}, with the exception that it encrypts the data
 | |
| in addition to signing it.
 | |
| 
 | |
| @example
 | |
|         data.data   = "hemligt";
 | |
|         data.length = 7;
 | |
| 
 | |
|         krb5_data_free (&packet);
 | |
| 
 | |
|         status = krb5_mk_priv (context,
 | |
|                                auth_context,
 | |
|                                &data,
 | |
|                                &packet,
 | |
|                                NULL);
 | |
|         if (status)
 | |
|                 krb5_err (context, 1, status, "krb5_mk_priv");
 | |
| @end example
 | |
| 
 | |
| And send it over the network.
 | |
| 
 | |
| @example
 | |
|         len = packet.length;
 | |
|         net_len = htonl(len);
 | |
| 
 | |
|         if (krb5_net_write (context, &sock, &net_len, 4) != 4)
 | |
|                 err (1, "krb5_net_write");
 | |
|         if (krb5_net_write (context, &sock, packet.data, len) != len)
 | |
|                 err (1, "krb5_net_write");
 | |
| 
 | |
| @end example
 | |
| 
 | |
| The server is using @manpage{krb5_rd_safe,3} and
 | |
| @manpage{krb5_rd_priv,3} to verify the signature and decrypt the packet.
 | |
| 
 | |
| @node Validating a password in a server application, API differences to MIT Kerberos, Walkthrough of a sample Kerberos 5 client, Programming with Kerberos
 | |
| @section Validating a password in an application
 | |
| 
 | |
| See the manual page for @manpage{krb5_verify_user,3}.
 | |
| 
 | |
| @node API differences to MIT Kerberos,  , Validating a password in a server application, Programming with Kerberos
 | |
| @section API differences to MIT Kerberos
 | |
| 
 | |
| This section is somewhat disorganised, but so far there is no overall
 | |
| structure to the differences, though some of the have their root in
 | |
| that Heimdal uses an ASN.1 compiler and MIT doesn't.
 | |
| 
 | |
| @subsection Principal and realms
 | |
| 
 | |
| Heimdal stores the realm as a @code{krb5_realm}, that is a @code{char *}.
 | |
| MIT Kerberos uses a @code{krb5_data} to store a realm.
 | |
| 
 | |
| In Heimdal @code{krb5_principal} doesn't contain the component
 | |
| @code{name_type}; it's instead stored in component
 | |
| @code{name.name_type}. To get and set the nametype in Heimdal, use
 | |
| @manpage{krb5_principal_get_type,3} and
 | |
| @manpage{krb5_principal_set_type,3}.
 | |
| 
 | |
| For more information about principal and realms, see
 | |
| @manpage{krb5_principal,3}.
 | |
| 
 | |
| @subsection Error messages
 | |
| 
 | |
| To get the error string, Heimdal uses
 | |
| @manpage{krb5_get_error_string,3} or, if @code{NULL} is returned,
 | |
| @manpage{krb5_get_err_text,3}. This is to return custom error messages
 | |
| (like ``Can't find host/datan.example.com@@EXAMPLE.COM in
 | |
| /etc/krb5.conf.'' instead of a ``Key table entry not found'' that
 | |
| @manpage{error_message,3} returns.
 | |
| 
 | |
| Heimdal uses a threadsafe(r) version of the com_err interface; the
 | |
| global @code{com_err} table isn't initialised.  Then
 | |
| @manpage{error_message,3} returns quite a boring error string (just
 | |
| the error code itself).
 | |
| 
 | |
| 
 | |
| @c @node Why you should use GSS-API for new applications, Walkthrough of a sample GSS-API client, Validating a password in a server application, Programming with Kerberos
 | |
| @c @section Why you should use GSS-API for new applications
 | |
| @c 
 | |
| @c SSPI, bah, bah, microsoft, bah, bah, almost GSS-API.
 | |
| @c 
 | |
| @c It would also be possible for other mechanisms then Kerberos, but that
 | |
| @c doesn't exist any other GSS-API implementations today.
 | |
| @c 
 | |
| @c @node Walkthrough of a sample GSS-API client, , Why you should use GSS-API for new applications, Programming with Kerberos
 | |
| @c @section Walkthrough of a sample GSS-API client
 | |
| @c 
 | |
| @c Write about how gssapi_clent.c works.
 |