Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
8.13.1 Problem
You need to authenticate using Kerberos. 8.13.2 Solution
If the client and the server are operating within the same Kerberos realm (or in separate realms, but cross-realm authentication is possible), you can use the user's credentials to authenticate from the client with the server. Both the client and the server must support this authentication method. The code presented in this recipe assumes you are using either the Heimdal or the MIT Kerberos implementation. It further assumes you are using Version 5, which we consider reasonable because Version 4 has been obsolete for so many years. We do not cover the Windows interface to Kerberos in this book because of the significant difference in the API compared to Heimdal and MIT implementations, as well as the complexity of the SSPI API that is required on Windows. We do, however, present an equivalent recipe for Windows on the book's web site. 8.13.3 Discussion
First, we define a structure primarily for convenience. After a successful authentication, several pieces of information are passed back from the Kerberos API. We store each of these pieces of information in a single structure rather than adding several additional arguments to our authentication functions. #include <krb5.h> typedef struct { krb5_context ctx; krb5_auth_context auth_ctx; krb5_ticket *ticket; } spc_krb5bundle_t; On the client side, only the ctx and auth_ctx fields will be initialized. On the server side, all three fields will be initialized. Before passing an spc_krb5bundle_t object to either spc_krb5_client( ) or spc_krb5_server( ), you must ensure that auth_ctx and ticket are initialized to NULL. If the ctx field is not NULL, it should be a valid krb5_context object, which will be used instead of creating a new one. Both the client and the server must be able to handle using Kerberos authentication. The code required for each side of the connection is very similar. On the client side, spc_krb5_client( ) will attempt to authenticate with the server. The code assumes that the user has already obtained a ticket-granting ticket from the appropriate Key Distribution Center (KDC), and that a credentials cache exists. The function spc_krb5_client( ) has the following signature: krb5_error_code spc_krb5_client(int sockfd, spc_krb5bundle_t *bundle, char *service, char *host, char *version); This function has the following arguments:
If authentication is successful, the return value from spc_krb5_client( ) will be 0, and the relevant fields in the spc_krb5bundle_t object will be filled in. The client may then proceed to use other Kerberos API functions to exchange encrypted and authenticated information with the server. Of particular interest is that a key suitable for use with a symmetric cipher is now available. (See Recipe 9.6 for an example of how to use the key effectively.) If any kind of error occurs while attempting to authenticate with the server, the return value from the following spc_krb5_client( ) function will be the error code returned by the Kerberos API function that failed. Complete lists of error codes are available in the Heimdal and MIT Kerberos header files. krb5_error_code spc_krb5_client(int sockfd, spc_krb5bundle_t *bundle, char *service, char *host, char *version) { int free_context = 0; krb5_principal server = 0; krb5_error_code rc; if (!bundle->ctx) { if ((rc = krb5_init_context(&(bundle->ctx))) != 0) goto error; free_context = 1; } if ((rc = krb5_sname_to_principal(bundle->ctx, host, service, KRB5_NT_SRV_HST, &server)) != 0) goto error; rc = krb5_sendauth(bundle->ctx, &(bundle->auth_ctx), &sockfd, version, 0, server, AP_OPTS_MUTUAL_REQUIRED, 0, 0, 0, 0, 0, 0); if (!rc) { krb5_free_principal(bundle->ctx, server); return 0; } error: if (server) krb5_free_principal(bundle->ctx, server); if (bundle->ctx && free_context) { krb5_free_context(bundle->ctx); bundle->ctx = 0; } return rc; } The code for the server side of the connection is similar to the client side, although it is somewhat simplified because most of the information in the exchange comes from the client. The function spc_krb5_server( ), listed later in this section, performs the server-side part of the authentication. It ultimately calls krb5_recvauth( ), which waits for the client to initiate an authenticate request. The function spc_krb5_server( ) has the following signature: krb5_error_code spc_krb5_server(int sockfd, spc_krb5bundle_t *bundle, char *service, char *version); This function has the following arguments:
If authentication is successful, the return value from spc_krb5_server( ) will be 0, and the relevant fields in the spc_krb5bundle_t object will be filled in. If any kind of error occurs while attempting to authenticate with the server, the return value from spc_krb5_server( ) will be the error code returned by the Kerberos API function that failed. krb5_error_code spc_krb5_server(int sockfd, spc_krb5bundle_t *bundle, char *service, char *version) { int free_context = 0; krb5_principal server = 0; krb5_error_code rc; if (!bundle->ctx) { if ((rc = krb5_init_context(&(bundle->ctx))) != 0) goto error; free_context = 1; } if ((rc = krb5_sname_to_principal(bundle->ctx, 0, service, KRB5_NT_SRV_HST, &server)) != 0) goto error; rc = krb5_recvauth(bundle->ctx, &(bundle->auth_ctx), &sockfd, version, server, 0, 0, &(bundle->ticket)); if (!rc) { krb5_free_principal(bundle->ctx, server); return 0; } error: if (server) krb5_free_principal(bundle->ctx, server); if (bundle->ctx && free_context) { krb5_free_context(bundle->ctx); bundle->ctx = 0; } return rc; } When a successful authentication is completed, an spc_krb5bundle_t object is filled with information resulting from the authentication. This information should eventually be cleaned up, of course. You may safely keep the information around as long as you need it, or you may clean it up at any time. If, once the authentication is complete, you don't need to retain any of the resulting information for further communication, you may even clean it up immediately. Call the function spc_krb5_cleanup( )when you no longer need any of the information contained in an spc_krb5bundle_t object. It will free all of the allocated resources in the proper order. void spc_krb5_cleanup(spc_krb5bundle_t *bundle) { if (bundle->ticket) { krb5_free_ticket(bundle->ctx, bundle->ticket); bundle->ticket = 0; } if (bundle->auth_ctx) { krb5_auth_con_free(bundle->ctx, bundle->auth_ctx); bundle->auth_ctx = 0; } if (bundle->ctx) { krb5_free_context(bundle->ctx); bundle->ctx = 0; } } 8.13.4 See Also
Recipe 9.6 |