Developer's Guide to Oracle Solaris Security

Working With Contexts in GSS-API

The two most significant tasks for GSS-API in providing security are to create security contexts and to protect data. After an application acquires the necessary credentials, a security context must be established. To establish a context, one application, typically a client, initiates the context, and another application, usually a server, accepts the context. Multiple contexts between peers are allowed.

The communicating applications establish a joint security context by exchanging authentication tokens. The security context is a pair of GSS-API data structures that contain information to be shared between the two applications. This information describes the state of each application in terms of security. A security context is required for protection of data.

Initiating a Context in GSS-API

The gss_init_sec_context() function is used to start a security context between an application and a remote peer. If successful, this function returns a context handle for the context to be established and a context-level token to send to the acceptor.

    Before calling gss_init_sec_context(), the client should perform the following tasks:

  1. Acquire credentials, if necessary, with gss_acquire_cred(). Typically, the client receives credentials at login. gss_acquire_cred() can only retrieve initial credentials from the running operating system.

  2. Import the name of the server into GSS-API internal format with gss_import_name(). See Names in GSS-API for more information about names and gss_import_name().

When calling gss_init_sec_context(), a client typically passes the following argument values:

Applications are not bound to use these default values. Additionally, a client can specify requirements for other security parameters with the req_flags argument. The full set of gss_init_sec_context() arguments is described below.

The context acceptor might require several handshakes to establish a context. That is, an acceptor can require the initiator to send more than one piece of context information before the context is fully established. Therefore, for portability, context initiation should always be done as part of a loop that checks whether the context has been fully established.

If the context is not complete, gss_init_sec_context() returns a major status code of GSS_C_CONTINUE_NEEDED. Therefore, a loop should use the return value from gss_init_sec_context() to test whether to continue the initiation loop.

The client passes context information to the server in the form of the output token, which is returned by gss_init_sec_context(). The client receives information back from the server as an input token. The input token can then be passed as an argument in subsequent calls of gss_init_sec_context(). If the received input token has a length of zero, however, then no more output tokens are required by the server.

Therefore, besides checking for the return status of gss_init_sec_context(), the loop should check the input token's length. If the length has a nonzero value, another token needs to be sent to the server. Before the loop begins, the input token's length should be initialized to zero. Either set the input token to GSS_C_NO_BUFFER or set the structure's length field to a value of zero.

The following pseudocode demonstrates an example of context establishment from the client side.

context = GSS_C_NO_CONTEXT
input token = GSS_C_NO_BUFFER

do

     call gss_init_sec_context(credential, context, name, input token, 
                                           output token, other args...)

     if (there's an output token to send to the acceptor)
          send the output token to the acceptor
          release the output token

     if (the context is not complete)
          receive an input token from the acceptor

     if (there's a GSS-API error)
          delete the context

until the context is complete

A real loop would be more complete with more extensive error-checking. See Establishing a Security Context With the Server for a real example of such a context-initiation loop. Additionally, the gss_init_sec_context(3GSS) man page provides a less generic example.

In general, the parameter values returned when a context is not fully established are those values that would be returned when the context is complete. See the gss_init_sec_context() man page for more information.

If gss_init_sec_context() completes successfully, GSS_S_COMPLETE is returned. If a context-establishment token is required from the peer application, GSS_S_CONTINUE_NEEDED is returned. If errors occur, error codes are returned as shown in the gss_init_sec_context(3GSS) man page.

If context initiation fails, the client should disconnect from the server.

Accepting a Context in GSS-API

The other half of context establishment is context acceptance, which is done through the gss_accept_sec_context() function. In a typical scenario, a server accepts a context that has been initiated by a client with gss_init_sec_context().

The main input to gss_accept_sec_context() is an input token from the initiator. The initiator returns a context handle as well as an output token to be returned to the initiator. Before gss_accept_sec_context() can be called, however, the server should acquire credentials for the service that was requested by the client. The server acquires these credentials with the gss_acquire_cred() function. Alternatively, the server can bypass explicit acquisition of credentials by specifying the default credential, that is, GSS_C_NO_CREDENTIAL, when the server calls gss_accept_sec_context().

When calling gss_accept_sec_context(), the server can set the following arguments as shown:

The full set of gss_accept_sec_context() arguments is described in the following paragraphs.

Security context establishment might require several handshakes. The initiator and acceptor often need to send more than one piece of context information before the context is fully established. Therefore, for portability, context acceptance should always be done as part of a loop that checks whether the context has been fully established. If the context is not yet established, gss_accept_sec_context() returns a major status code of GSS_C_CONTINUE_NEEDED. Therefore, a loop should use the value that was returned by gss_accept_sec_context() to test whether to continue the acceptance loop.

The context acceptor returns context information to the initiator in the form of the output token that was returned by gss_accept_sec_context(). Subsequently, the acceptor can receive additional information from the initiator as an input token. The input token is then passed as an argument to subsequent gss_accept_sec_context() calls. When gss_accept_sec_context() has no more tokens to send to the initiator, an output token with a length of zero is returned. Besides checking for the return status gss_accept_sec_context(), the loop should check the output token's length to see whether another token must be sent. Before the loop begins, the output token's length should be initialized to zero. Either set the output token to GSS_C_NO_BUFFER, or set the structure's length field to a value of zero.

The following pseudocode demonstrates an example of context establishment from the server side.

context = GSS_C_NO_CONTEXT
output token = GSS_C_NO_BUFFER

do

     receive an input token from the initiator

     call gss_accept_sec_context(context, cred handle, input token,
                                                output token, other args...)

     if (there's an output token to send to the initiator)
          send the output token to the initiator
          release the output token

     if (there's a GSS-API error)
          delete the context

until the context is complete

A real loop would be more complete with more extensive error-checking. See Establishing a Security Context With the Server for a real example of such a context-acceptance loop. Additionally, the gss_accept_sec_context() man page provides an example.

Again, GSS-API does not send or receive tokens. Tokens must be handled by the application. Examples of token-transferring functions are found in Miscellaneous GSS-API Sample Functions.

gss_accept_sec_context() returns GSS_S_COMPLETE if it completes successfully. If the context is not complete, the function returns GSS_S_CONTINUE_NEEDED. If errors occur, the function returns error codes. For more information, see the gss_accept_sec_context(3GSS) man page.

Using Other Context Services in GSS-API

The gss_init_sec_context() function enables an application to request additional data protection services beyond basic context establishment. These services are requested through the req_flags argument to gss_init_sec_context().

Not all mechanisms offer all these services. The ret_flags argument for gss_init_sec_context() indicates which services are available in a given context. Similarly, the context acceptor examines the ret_flags value that is returned by gss_accept_sec_context() to determine the available services. The additional services are explained in the following sections.

Delegating a Credential in GSS-API

If permitted, a context initiator can request that the context acceptor act as a proxy. In such a case, the acceptor can initiate further contexts on behalf of the initiator.

Suppose someone on Machine A wants to rlogin to Machine B, and then rlogin from Machine B to Machine C. Depending on the mechanism, the delegated credential identifies B either as A or B as proxy for A.

If delegation is permitted, ret_flags can be set to GSS_C_DELEG_FLAG. The acceptor receives a delegated credential as the delegated_cred_handle argument of gss_accept_sec_context(). Delegating a credential is not the same as exporting a context. See Exporting and Importing Contexts in GSS-API. One difference is that an application can delegate that application's credentials multiple times simultaneously, while a context can only be held by one process at a time.

Performing Mutual Authentication Between Peers in GSS-API

A user who transfers files to an ftp site typically does not need proof of the site's identity. On the other hand, a user who is required to provide a credit card number to an application would want definite proof of the receiver's identity. In such a case, mutual authentication is required. Both the context initiator and the acceptor must prove their identities.

A context initiator can request mutual authentication by setting the gss_init_sec_context() req_flags argument to the value GSS_C_MUTUAL_FLAG. If mutual authentication has been authorized, the function indicates authorization by setting the ret_flags argument to this value. If mutual authentication is requested but not available, the initiating application is responsible for responding accordingly. GSS-API does not automatically terminate a context when mutual authentication is requested but unavailable. Also, some mechanisms always perform mutual authentication even without a specific request.

Performing Anonymous Authentication in GSS-API

In normal use of GSS-API, the initiator's identity is made available to the acceptor as a part of context establishment. However, a context initiator can request that its identity not be revealed to the context acceptor.

For example, consider an application that provides unrestricted access to a medical database. A client of such a service might want to authenticate the service. This approach would establish trust in any information that is retrieved from the database. The client might not want to expose its identity due to privacy concerns, for example.

To request anonymity, set the req_flags argument of gss_init_sec_context() to GSS_C_ANON_FLAG. To verify whether anonymity is available, check the ret_flags argument to gss_init_sec_context() or gss_accept_sec_context() to see whether GSS_C_ANON_FLAG is returned.

When anonymity is in effect, calling gss_display_name() on a client name that was returned by gss_accept_sec_context() or gss_inquire_context() produces a generic anonymous name.


Note –

An application has the responsibility to take appropriate action if anonymity is requested but not permitted. GSS-API does not terminate a context in such a case.


Using Channel Bindings in GSS-API

For many applications, basic context establishment is sufficient to assure proper authentication of a context initiator. In cases where additional security is desired, GSS-API offers the use of channel bindings. Channel bindings are tags that identify the particular data channel that is used. Specifically, channel bindings identify the origin and endpoint, that is, the initiator and acceptor of the context. Because the tags are specific to the originator and recipient applications, such tags offer more proof of a valid identity.

Channel bindings are pointed to by the gss_channel_bindings_t data type, which is a pointer to a gss_channel_bindings_struct structure as shown below.

typedef struct gss_channel_bindings_struct {
OM_uint32       initiator_addrtype;
gss_buffer_desc initiator_address;
OM_uint32       acceptor_addrtype;
gss_buffer_desc acceptor_address;
gss_buffer_desc application_data;
} *gss_channel_bindings_t;

The first two fields are the address of the initiator and an address type that identifies the format in which the initiator's address is being sent. For example, initiator_addrtype might be sent to GSS_C_AF_INET to indicate that initiator_address is in the form of an Internet address, that is, an IP address. Similarly, the third and fourth fields indicate the address and address type of the acceptor. The final field, application_data, can be used by the application as needed. Set application_data to GSS_C_NO_BUFFER if application_data is not going to be used. If an application does not specify an address, that application should set the address type field to GSS_C_AF_NULLADDR. The Address Types for Channel Bindings section has a list of valid address type values.

The address types indicate address families rather than specific addressing formats. For address families that contain several alternative address forms, the initiator_address and acceptor_address fields must contain sufficient information to determine which form is used. When not otherwise specified, addresses should be specified in network byte-order, that is, native byte-ordering for the address family.

To establish a context that uses channel bindings, the input_chan_bindings argument for gss_init_sec_context() should point to an allocated channel bindings structure. The structure's fields are concatenated into an octet string, and a MIC is derived. This MIC is then bound to the output token. The application then sends the token to the context acceptor. After receiving the token, the acceptor calls gss_accept_sec_context(). See Accepting a Context in GSS-API for more information. gss_accept_sec_context() calculates a MIC for the received channel bindings. gss_accept_sec_context() then returns GSS_C_BAD_BINDINGS if the MIC does not match.

Because gss_accept_sec_context() returns the transmitted channel bindings, an acceptor can use these values to perform security checking. For example, the acceptor could check the value of application_data against code words that are kept in a secure database.


Note –

An underlying mechanism might not provide confidentiality for channel binding information. Therefore, an application should not include sensitive information as part of channel bindings unless confidentiality is ensured. To test for confidentiality, an application can check the ret_flags argument of gss_init_sec_context() or gss_accept_sec_context(). The values GSS_C_CONF_FLAG and GSS_C_PROT_READY_FLAG indicate confidentiality. See Initiating a Context in GSS-API or Accepting a Context in GSS-API for information on ret_flags.


Individual mechanisms can impose additional constraints on the addresses and address types that appear in channel bindings. For example, a mechanism might verify that the initiator_address field of the channel bindings to be returned to gss_init_sec_context(). Portable applications should therefore provide the correct information for the address fields. If the correct information cannot be determined, then GSS_C_AF_NULLADDR should be specified as the address types.

Exporting and Importing Contexts in GSS-API

GSS-API provides the means for exporting and importing contexts. This ability enables a multiprocess application, usually the context acceptor, to transfer a context from one process to another. For example, an acceptor might have one process that listens for context initiators and another that uses the data that is sent in the context. The Using the test_import_export_context() Function section shows how a context can be saved and restored with these functions.

The function gss_export_sec_context() creates an interprocess token that contains information about the exported context. See Interprocess Tokens in GSS-API for more information. The buffer to receive the token should be set to GSS_C_NO_BUFFER before gss_export_sec_context() is called.

The application then passes the token on to the other process. The new process accepts the token and passes that token to gss_import_sec_context(). The same functions that are used to pass tokens between applications can often be used to pass tokens between processes as well.

Only one instantiation of a security process can exist at a time. gss_export_sec_context() deactivates the exported context and sets the context handle to GSS_C_NO_CONTEXT. gss_export_sec_context() also deallocates any process-wide resources that are associated with that context. If the context exportation cannot be completed, gss_export_sec_context() leaves the existing security context unchanged and does not return an interprocess token.

Not all mechanisms permit contexts to be exported. An application can determine whether a context can be exported by checking the ret_flags argument to gss_accept_sec_context() or gss_init_sec_context(). If this flag is set to GSS_C_TRANS_FLAG, then the context can be exported. (See Accepting a Context in GSS-API and Initiating a Context in GSS-API.)

Figure 4–6 shows how a multiprocess acceptor might use context exporting to multitask. In this case, Process 1 receives and processes tokens. This step separates the context-level tokens from the data tokens and passes the tokens on to Process 2. Process 2 deals with data in an application-specific way. In this illustration, the clients have already obtained export tokens from gss_init_sec_context(). The clients pass the tokens to a user-defined function, send_a_token(), which indicates whether the token to be transmitted is a context-level token or a message token. send_a_token() transmits the tokens to the server. Although not shown here, send_a_token() would presumably be used to pass tokens between threads as well.

Figure 4–6 Exporting Contexts: Multithreaded Acceptor Example

Diagram shows how a multiprocess acceptor can separate
context and data tokens, and pass them on to a second process.

Obtaining Context Information in GSS-API

GSS-API provides a function, gss_inquire_context(3GSS), that obtains information about a given security context. Note that the context does not need to be complete.

Given a context handle, gss_inquire_context() provides the following information about context: