Oracle Solaris Security for Developers Guide

Chapter 6 GSS-API Server Example

This chapter presents a walk-through of the source code for the gss-server sample program. The following topics are covered:

GSSAPI Server Example Overview

The sample server-side program gss-server works in conjunction with gss-client, which is described in the previous chapter. The basic purpose of gss-server is to receive, sign, and return the wrapped message from gssapi-client.

The following sections provide a step-by-step description of how gss-server works. Because gss-server is a sample program for demonstrating GSSAPI functionality, only relevant parts of the program are discussed in detail. The complete source code for the two applications appears in the appendix and can be downloaded from

http://developers.sun.com/prodtech/solaris/downloads/index.html

GSSAPI Server Example Structure

The gss-structure application performs the following steps:

  1. Parses the command line.

  2. If a mechanism is specified, translates the mechanism name to internal format.

  3. Acquires credentials for the caller.

  4. Checks to see whether the user has specified using the inetd daemon for connecting.

  5. Makes a connection with the client.

  6. Receives the data from the client.

  7. Signs and returns the data.

  8. Releases namespaces and exits.

Running the GSSAPI Server Example

gss-server takes this form on the command line

gss-server [-port port] [-verbose] [-inetd] [-once] [-logfile file] \
                 [-mech mechanism] service-name

A typical command line might look like the following example:


% gss-server -port 8080 -once -mech kerberos_v5 erebos.eng nfs "hello"

GSSAPI Server Example: main() Function

The gss-server main() function performs the following tasks:


Note –

The source code for this example is also available through the Sun download center. See http://www.sun.com/download/products.xml?id=41912db5.



Example 6–1 gss-server Example: main()

int
main(argc, argv)
     int argc;
     char **argv;
{
     char *service_name;
     gss_cred_id_t server_creds;
     OM_uint32 min_stat;
     u_short port = 4444;
     int s;
     int once = 0;
     int do_inetd = 0;

     log = stdout;
     display_file = stdout;

     /* Parse command-line arguments. */
     argc--; argv++;
     while (argc) {
     if (strcmp(*argv, "-port") == 0) {
          argc--; argv++;
          if (!argc) usage();
          port = atoi(*argv);
     } else if (strcmp(*argv, "-verbose") == 0) {
          verbose = 1;
     } else if (strcmp(*argv, "-once") == 0) {
          once = 1;
     } else if (strcmp(*argv, "-inetd") == 0) {
          do_inetd = 1;
     } else if (strcmp(*argv, "-logfile") == 0) {
          argc--; argv++;
          if (!argc) usage();
          log = fopen(*argv, "a");
          display_file = log;
          if (!log) {
          perror(*argv);
          exit(1);
          }
     } else
          break;
     argc--; argv++;
     }
     if (argc != 1)
          usage();

     if ((*argv)[0] == '-')
          usage();

     service_name = *argv;

     /* Acquire service credentials. */
     if (server_acquire_creds(service_name, &server_creds) < 0)
          return -1;

     if (do_inetd) {
          close(1);
          close(2);
          /* Sign and return message. */
          sign_server(0, server_creds);
          close(0);
     } else {
          int stmp;

          if ((stmp = create_socket(port)) >= 0) {
              do {
                  /* Accept a TCP connection */
                  if ((s = accept(stmp, NULL, 0)) < 0) {
                      perror("accepting connection");
                      continue;
                  }
                  /* This return value is not checked, because there is
                     not really anything to do if it fails. */
                  sign_server(s, server_creds);
                  close(s);
              } while (!once);

              close(stmp);
          }
     }

     /* Close down and clean up. */
     (void) gss_release_cred(&min_stat, &server_creds);

     /*NOTREACHED*/
     (void) close(s);
     return 0;
}

Acquiring Credentials

Credentials are created by the underlying mechanisms rather than by the client application, server application, or GSS-API. A client program often has credentials that are obtained at login. A server always needs to acquire credentials explicitly.

The gss-server program has a function, server_acquire_creds(), to get the credentials for the service to be provided. server_acquire_creds() takes as input the name of the service and the security mechanism to be used. server_acquire_creds() then returns the credentials for the service.

server_acquire_creds() uses the GSS-API function gss_acquire_cred() to get the credentials for the service that the server provides. Before server_acquire_creds() accesses gss_acquire_cred(), server_acquire_creds() must take care of two tasks:

  1. Checking for a list of mechanisms and reducing the list to a single mechanism for the purpose of getting a credential.

    If a single credential can be shared by multiple mechanisms, the gss_acquire_cred() function returns credentials for all those mechanisms. Therefore, gss_acquire_cred() takes as input a set of mechanisms. (See Working With Credentials in GSS-API.) In most cases, however, including this one, a single credential might not work for multiple mechanisms. In the gss-server program, either a single mechanism is specified on the command line or else the default mechanism is used. Therefore, the first task is to make sure that the set of mechanisms that was passed to gss_acquire_cred() contains a single mechanism, default or otherwise, as follows:

    if (mechOid != GSS_C_NULL_OID) {
         desiredMechs = &mechOidSet;
         mechOidSet.count = 1;
         mechOidSet.elements = mechOid;
    } else
         desiredMechs = GSS_C_NULL_OID_SET;

    GSS_C_NULL_OID_SET indicates that the default mechanism should be used.

  2. Translating the service name into GSS-API format.

    Because gss_acquire_cred() takes the service name in the form of a gss_name_t structure, the name of the service must be imported into that format. The gss_import_name() function performs this translation. Because this function, like all GSS-API functions, requires arguments to be GSS-API types, the service name has to be copied to a GSS-API buffer first, as follows:

         name_buf.value = service_name;
         name_buf.length = strlen(name_buf.value) + 1;
         maj_stat = gss_import_name(&min_stat, &name_buf,
                    (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &server_name);
         if (maj_stat != GSS_S_COMPLETE) {
              display_status("importing name", maj_stat, min_stat);
              if (mechOid != GSS_C_NO_OID)
                    gss_release_oid(&min_stat, &mechOid);
              return -1;
         }

    Note again the use of the nonstandard function gss_release_oid().

    The input is the service name as a string in name_buf. The output is the pointer to a gss_name_t structure, server_name. The third argument, GSS_C_NT_HOSTBASED_SERVICE, is the name type for the string in name_buf. In this case, the name type indicates that the string should be interpreted as a service of the format service@host.

After these tasks have been performed, the server program can call gss_acquire_cred():

maj_stat = gss_acquire_cred(&min_stat, server_name, 0,
                                 desiredMechs, GSS_C_ACCEPT,
                                 server_creds, NULL, NULL);

The following source code illustrates the server_acquire_creds() function.


Note –

The source code for this example is also available through the Sun download center. See http://www.sun.com/download/products.xml?id=41912db5.



Example 6–2 Sample Code for server_acquire_creds() Function

/*
 * Function: server_acquire_creds
 *
 * Purpose: imports a service name and acquires credentials for it
 *
 * Arguments:
 *
 *      service_name    (r) the ASCII service name
        mechType        (r) the mechanism type to use
 *      server_creds    (w) the GSS-API service credentials
 *
 * Returns: 0 on success, -1 on failure
 *
 * Effects:
 *
 * The service name is imported with gss_import_name, and service
 * credentials are acquired with gss_acquire_cred.  If either operation
 * fails, an error message is displayed and -1 is returned; otherwise,
 * 0 is returned.
 */
int server_acquire_creds(service_name, mechOid, server_creds)
     char *service_name;
     gss_OID mechOid;
     gss_cred_id_t *server_creds;
{
     gss_buffer_desc name_buf;
     gss_name_t server_name;
     OM_uint32 maj_stat, min_stat;
     gss_OID_set_desc mechOidSet;
     gss_OID_set desiredMechs = GSS_C_NULL_OID_SET;

     if (mechOid != GSS_C_NULL_OID) {
                desiredMechs = &mechOidSet;
                mechOidSet.count = 1;
                mechOidSet.elements = mechOid;
     } else
                desiredMechs = GSS_C_NULL_OID_SET;

     name_buf.value = service_name;
     name_buf.length = strlen(name_buf.value) + 1;
     maj_stat = gss_import_name(&min_stat, &name_buf,
                (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &server_name);
     if (maj_stat != GSS_S_COMPLETE) {
          display_status("importing name", maj_stat, min_stat);
          if (mechOid != GSS_C_NO_OID)
                gss_release_oid(&min_stat, &mechOid);
          return -1;
     }

     maj_stat = gss_acquire_cred(&min_stat, server_name, 0,
                                 desiredMechs, GSS_C_ACCEPT,
                                 server_creds, NULL, NULL);

     if (maj_stat != GSS_S_COMPLETE) {
          display_status("acquiring credentials", maj_stat, min_stat);
          return -1;
     }

     (void) gss_release_name(&min_stat, &server_name);

     return 0;
}

Checking for inetd

Having acquired credentials for the service, gss-server checks to see whether the user has specified inetd. The main function checks for inetd as follows:

if (do_inetd) {
	 close(1);
	 close(2);

If the user has specified to use inetd, then the program closes the standard output and standard error. gss-server then calls sign_server() on the standard input, which inetd uses to pass connections. Otherwise, gss-server creates a socket, accepts the connection for that socket with the TCP function accept(), and calls sign_server() on the file descriptor that is returned by accept().

If inetd is not used, the program creates connections and contexts until the program is terminated. However, if the user has specified the -once option, the loop terminates after the first connection.

Receiving Data From a Client

After checking for inetd, the gss-server program then calls sign_server(), which does the main work of the program. sign_server() first establishes the context by calling server_establish_context().

sign_server() performs the following tasks:

These tasks are described in the subsequent sections. The following source code illustrates the sign_server() function.


Note –

The source code for this example is also available through the Sun download center. See http://www.sun.com/download/products.xml?id=41912db5.



Example 6–3 sign_server() Function

int sign_server(s, server_creds)
     int s;
     gss_cred_id_t server_creds;
{
     gss_buffer_desc client_name, xmit_buf, msg_buf;
     gss_ctx_id_t context;
     OM_uint32 maj_stat, min_stat;
     int i, conf_state, ret_flags;
     char	*cp;
     
     /* Establish a context with the client */
     if (server_establish_context(s, server_creds, &context,
                  &client_name, &ret_flags) < 0)
     return(-1);

     printf("Accepted connection: \"%.*s\"\n",
         (int) client_name.length, (char *) client_name.value);
     (void) gss_release_buffer(&min_stat, &client_name);

     for (i=0; i < 3; i++)
         if (test_import_export_context(&context))
             return -1;

     /* Receive the sealed message token */
     if (recv_token(s, &xmit_buf) < 0)
         return(-1);

     if (verbose && log) {
         fprintf(log, "Sealed message token:\n");
         print_token(&xmit_buf);
     }

     maj_stat = gss_unwrap(&min_stat, context, &xmit_buf, &msg_buf,
                  &conf_state, (gss_qop_t *) NULL);
     if (maj_stat != GSS_S_COMPLETE) {
         display_status("unsealing message", maj_stat, min_stat);
         return(-1);
     } else if (! conf_state) {
         fprintf(stderr, "Warning!  Message not encrypted.\n");
     }

     (void) gss_release_buffer(&min_stat, &xmit_buf);

     fprintf(log, "Received message: ");
     cp = msg_buf.value;
     if ((isprint(cp[0]) || isspace(cp[0])) &&
         (isprint(cp[1]) || isspace(cp[1]))) {
         fprintf(log, "\"%.*s\"\n", msg_buf.length, msg_buf.value);
     } else {
         printf("\n");
         print_token(&msg_buf);
     }

     /* Produce a signature block for the message */
     maj_stat = gss_get_mic(&min_stat, context, GSS_C_QOP_DEFAULT,
                  &msg_buf, &xmit_buf);
     if (maj_stat != GSS_S_COMPLETE) {
         display_status("signing message", maj_stat, min_stat);
         return(-1);
     }

     (void) gss_release_buffer(&min_stat, &msg_buf);

     /* Send the signature block to the client */
     if (send_token(s, &xmit_buf) < 0)
         return(-1);

     (void) gss_release_buffer(&min_stat, &xmit_buf);

     /* Delete context */
     maj_stat = gss_delete_sec_context(&min_stat, &context, NULL);
     if (maj_stat != GSS_S_COMPLETE) {
         display_status("deleting context", maj_stat, min_stat);
         return(-1);
     }

     fflush(log);

     return(0);
}

Accepting a Context

Establishing a context typically involves a series of token exchanges between the client and the server. Both context acceptance and context initialization should be performed in loops to maintain program portability. The loop for accepting a context is very similar to the loop for establishing a context, although in reverse. Compare with Establishing a Security Context With the Server.

The following source code illustrates the server_establish_context() function.


Note –

The source code for this example is also available through the Sun download center. See http://www.sun.com/download/products.xml?id=41912db5.



Example 6–4 server_establish_context() Function

/*
 * Function: server_establish_context
 *
 * Purpose: establishes a GSS-API context as a specified service with
 * an incoming client, and returns the context handle and associated
 * client name
 *
 * Arguments:
 *
 *      s               (r) an established TCP connection to the client
 *      service_creds   (r) server credentials, from gss_acquire_cred
 *      context         (w) the established GSS-API context
 *      client_name     (w) the client's ASCII name
 *
 * Returns: 0 on success, -1 on failure
 *
 * Effects:
 *
 * Any valid client request is accepted.  If a context is established,
 * its handle is returned in context and the client name is returned
 * in client_name and 0 is returned.  If unsuccessful, an error
 * message is displayed and -1 is returned.
 */
int server_establish_context(s, server_creds, context, client_name, ret_flags)
     int s;
     gss_cred_id_t server_creds;
     gss_ctx_id_t *context;
     gss_buffer_t client_name;
     OM_uint32 *ret_flags;
{
     gss_buffer_desc send_tok, recv_tok;
     gss_name_t client;
     gss_OID doid;
     OM_uint32 maj_stat, min_stat, acc_sec_min_stat;
     gss_buffer_desc    oid_name;

     *context = GSS_C_NO_CONTEXT;
     
     do {
          if (recv_token(s, &recv_tok) < 0)
               return -1;

          if (verbose && log) {
              fprintf(log, "Received token (size=%d): \n", recv_tok.length);
              print_token(&recv_tok);
          }

          maj_stat =
               gss_accept_sec_context(&acc_sec_min_stat,
                                      context,
                                      server_creds,
                                      &recv_tok,
                                      GSS_C_NO_CHANNEL_BINDINGS,
                                      &client,
                                      &doid,
                                      &send_tok,
                                      ret_flags,
                                      NULL,     /* ignore time_rec */
                                      NULL);    /* ignore del_cred_handle */

          (void) gss_release_buffer(&min_stat, &recv_tok);

          if (send_tok.length != 0) {
               if (verbose && log) {
                    fprintf(log,
                          "Sending accept_sec_context token (size=%d):\n",
                          send_tok.length);
                    print_token(&send_tok);
               }
               if (send_token(s, &send_tok) < 0) {
                    fprintf(log, "failure sending token\n");
                    return -1;
               }

               (void) gss_release_buffer(&min_stat, &send_tok);
          }
          if (maj_stat!=GSS_S_COMPLETE && maj_stat!=GSS_S_CONTINUE_NEEDED) {
               display_status("accepting context", maj_stat,
                              acc_sec_min_stat);
               if (*context == GSS_C_NO_CONTEXT)
                       gss_delete_sec_context(&min_stat, context,
                                              GSS_C_NO_BUFFER);
               return -1;
          }

          if (verbose && log) {
              if (maj_stat == GSS_S_CONTINUE_NEEDED)
                  fprintf(log, "continue needed...\n");
              else
                  fprintf(log, "\n");
              fflush(log);
          }
     } while (maj_stat == GSS_S_CONTINUE_NEEDED);

     /* display the flags */
     display_ctx_flags(*ret_flags);

     if (verbose && log) {
         maj_stat = gss_oid_to_str(&min_stat, doid, &oid_name);
         if (maj_stat != GSS_S_COMPLETE) {
             display_status("converting oid->string", maj_stat, min_stat);
             return -1;
         }
         fprintf(log, "Accepted connection using mechanism OID %.*s.\n",
                 (int) oid_name.length, (char *) oid_name.value);
         (void) gss_release_buffer(&min_stat, &oid_name);
     }

     maj_stat = gss_display_name(&min_stat, client, client_name, &doid);
     if (maj_stat != GSS_S_COMPLETE) {
          display_status("displaying name", maj_stat, min_stat);
          return -1;
     }
     maj_stat = gss_release_name(&min_stat, &client);
     if (maj_stat != GSS_S_COMPLETE) {
          display_status("releasing name", maj_stat, min_stat);
          return -1;
     }
     return 0;
}

The sign_server() function uses the following source code to call server_establish_context() to accept the context.

/* Establish a context with the client */
     if (server_establish_context(s, server_creds, &context,
				  &client_name, &ret_flags) < 0)
	return(-1);

The server_establish_context() function first looks for a token that the client sends as part of the context initialization process. Because, GSS-API does not send or receive tokens itself, programs must have their own routines for performing these tasks. The server uses recv_token() for receiving the token:

     do {
          if (recv_token(s, &recv_tok) < 0)
               return -1;

Next, server_establish_context() calls the GSS-API function gss_accept_sec_context():

     maj_stat = gss_accept_sec_context(&min_stat,
                                      context,
                                      server_creds,
                                      &recv_tok,
                                      GSS_C_NO_CHANNEL_BINDINGS,
                                      &client,
                                      &doid,
                                      &send_tok,
                                      ret_flags,
                                      NULL,     /* ignore time_rec */
                                      NULL);    /* ignore del_cred_handle */

The acceptance loop continues, barring any errors, as long as gss_accept_sec_context() sets maj_stat to GSS_S_CONTINUE_NEEDED. If maj_stat is not equal to that value or to GSS_S_COMPLETE, a problem exists and the loop exits.

gss_accept_sec_context() returns a positive value for the length of send_tok whether a token exists to send back to the client. The next step is to see a token exists to be sent, and, if so, to send the token:

     if (send_tok.length != 0) {
          . . .
          if (send_token(s, &send_tok) < 0) {
               fprintf(log, "failure sending token\n");
               return -1;
          }

          (void) gss_release_buffer(&min_stat, &send_tok);
          }

Unwrapping the Message

After accepting the context, the sign_server() receives the message that has been sent by the client. Because the GSS-API does not provide a function for receiving tokens, the program uses the recv_token() function:

if (recv_token(s, &xmit_buf) < 0)
     return(-1);

Because the message might be encrypted, the program uses the GSS-API function gss_unwrap() for unwrapping:

maj_stat = gss_unwrap(&min_stat, context, &xmit_buf, &msg_buf,
                           &conf_state, (gss_qop_t *) NULL);
     if (maj_stat != GSS_S_COMPLETE) {
        display_status("unwrapping message", maj_stat, min_stat);
        return(-1);
     } else if (! conf_state) {
        fprintf(stderr, "Warning!  Message not encrypted.\n");
     }

     (void) gss_release_buffer(&min_stat, &xmit_buf);

gss_unwrap() takes the message that recv_token() has placed in xmit_buf, translates the message, and puts the result in msg_buf. Two arguments to gss_unwrap() are noteworthy. conf_state is a flag to indicate whether confidentiality, that is, encryption, has been applied to this message. The final NULL indicates that the program does not need to know that the QOP that was used to protect the message.

Signing and Returning the Message

At this point, the sign_server() function needs to sign the message. Signing a message entails returning the message's Message Integrity Code or MIC to the client. Returning the message proves that the message was sent and was unwrapped successfully. To obtain the MIC, sign_server() uses the function gss_get_mic():

maj_stat = gss_get_mic(&min_stat, context, GSS_C_QOP_DEFAULT,
                            &msg_buf, &xmit_buf);

gss_get_mic() looks at the message in msg_buf, produces the MIC, and stores the MIC in xmit_buf. The server then sends the MIC back to the client with send_token(). The client verifies the MIC with gss_verify_mic(). See Reading and Verifying a Signature Block From a GSS-API Client.

Finally, sign_server() performs some cleanup. sign_server() releases the GSS-API buffers msg_buf and xmit_buf with gss_release_buffer(). Then sign_server() destroys the context with gss_delete_sec_context().

Using the test_import_export_context() Function

GSS-API allows you to export and import contexts. These activities enable you to share a context between different processes in a multiprocess program. sign_server() contains a proof-of-concept function, test_import_export_context(), that illustrates how exporting and importing contexts works. test_import_export_context() does not pass a context between processes. Instead, test_import_export_context() displays the amount of time to export and then import a context. Although an artificial function, test_import_export_context() does indicate how to use the GSS-API importing and exporting functions. test_import_export_context() also shows how to use timestamps with regard to manipulating contexts.

The source code for test_import_export_context() is shown in the following example.


Note –

The source code for this example is also available through the Sun download center. See http://www.sun.com/download/products.xml?id=41912db5.



Example 6–5 test_import_export_context()

int test_import_export_context(context)
        gss_ctx_id_t *context;
{
        OM_uint32       min_stat, maj_stat;
        gss_buffer_desc context_token, copied_token;
        struct timeval tm1, tm2;

        /*
         * Attempt to save and then restore the context.
         */
        gettimeofday(&tm1, (struct timezone *)0);
        maj_stat = gss_export_sec_context(&min_stat, context, &context_token);
        if (maj_stat != GSS_S_COMPLETE) {
                display_status("exporting context", maj_stat, min_stat);
                return 1;
        }
        gettimeofday(&tm2, (struct timezone *)0);
        if (verbose && log)
                fprintf(log, "Exported context: %d bytes, %7.4f seconds\n",
                        context_token.length, timeval_subtract(&tm2, &tm1));
        copied_token.length = context_token.length;
        copied_token.value = malloc(context_token.length);
        if (copied_token.value == 0) {
            fprintf(log, "Couldn't allocate memory to copy context token.\n");
            return 1;
        }
        memcpy(copied_token.value, context_token.value, copied_token.length);
        maj_stat = gss_import_sec_context(&min_stat, &copied_token, context);
        if (maj_stat != GSS_S_COMPLETE) {
                display_status("importing context", maj_stat, min_stat);
                return 1;
        }
        free(copied_token.value);
        gettimeofday(&tm1, (struct timezone *)0);
        if (verbose && log)
                fprintf(log, "Importing context: %7.4f seconds\n",
                        timeval_subtract(&tm1, &tm2));
        (void) gss_release_buffer(&min_stat, &context_token);
        return 0;
}

Cleanup in the GSSAPI Server Example

Back in the main() function, the application deletes the service credential with gss_release_cred(). If an OID for the mechanism has been specified, the program deletes the OID with gss_release_oid() and exits.

     (void) gss_release_cred(&min_stat, &server_creds);