OIDs
Object Identifiers (OIDs) are used to store the following kinds of data: security mechanisms, QOPs (Quality of Protection values), and name types. OIDs are stored in the GSS-API gss_OID_desc structure; the GSS-API provides a pointer to the structure, gss_OID, as shown here.
Example 1-3 OIDs
typedef struct gss_OID_desc_struct { OM_uint32 length; void *elements; } gss_OID_desc, *gss_OID; |
Further, one or more OIDs might be contained in a gss_OID_set_desc structure.
Example 1-4 OID Sets
typedef struct gss_OID_set_desc_struct { size_t count; gss_OID elements; } gss_OID_set_desc, *gss_OID_set; |
Caution - Applications should not attempt to deallocate OIDs with free().
Mechanisms and QOPs
Although the GSS-API allows applications to choose which underlying security mechanism to use, applications should use the default mechanism selected by the GSS-API if possible. Likewise, the GSS-API allows an application to specify the QOP it wants for protecting data -- a QOP (Quality of Protection) is the algorithm used for encrypting data or generating a cryptographic identification tag -- the default QOP should be used if possible. The default mechanism is represented by passing the value GSS_C_NULL_OID to functions that expect a mechanism or QOP as an argument.
Caution - Specifying a security mechanism or QOP explicitly more or less defeats the purpose of using the GSS-API, because it limits the
portability of an application. Other implementations of the GSS-API may not support that QOP or mechanism, or they may support it in limited or unexpected ways. Nonetheless, Appendix C, Specifying an OID briefly discusses how to find out which mechanisms and QOPs are available, and how to choose one.
Name Types
Besides QOPs and security mechanisms, OIDs are also used to indicate name types, which indicate the format for an associated name. For example, the function gss_import_name(), which converts the name of a principal from a string to a gss_name_t type, takes as one argument the format of the string to be converted. If the name type is (for example) GSS_C_NT_HOSTBASED_SERVICE, then the function knows that the name being input is of the form "service@host", as in "nfs@swim2birds"; if it's equal to, for instance, GSS_C_NT_EXPORT_NAME, then the function knows that it's a GSS-API exported name. Applications can find out which name types are available for a given mechanism with the gss_inquire_names_for_mech() function. A list of name types used by the GSS-API is given in "Name Types".
Status Codes
All GSS-API functions return two types of codes that provide information on the function's success or failure. Both types of status codes are returned as OM_uint32 values. The two types of return codes are as follows:
Major-status codes. These are codes that indicate: a) generic GSS-API routine errors (such as giving a routine an invalid mechanism); b) calling errors specific to a particular GSS-API language binding (namely, a function argument that cannot be read, cannot be written, or is malformed); or c) both. Additionally, major-status codes can provide supplementary information about a routine's status -- that an operation is not finished, for example, or that a token has been sent out of order. If no errors occur, the routine returns a major status value of GSS_S_COMPLETE.
Major-status codes are returned as shown here:
OM_uint32 major_status ; /* status returned by GSS-API */ major_status = gss_generic_function(arg1, arg2 ...);
Major status return codes can be processed like any other OM_uint32. For example:
OM_uint32 maj_stat; maj_sta = gss_generic_function(arg1, arg2 ...); if (maj_stat == GSS_CREDENTIALS_EXPIRED) <do something...>
They can be processed with the macros GSS_ROUTINE_ERROR(), GSS_CALLING_ERROR(), and GSS_SUPPLEMENTARY_INFO(). "GSS-API Status Codes" explains how to read major-status codes and contains a list of GSS-API status codes.
Minor status codes. These are returned by the underlying mechanism, and so are not specifically documented in this manual.
Every GSS-API function has as its first argument an OM_uint32 for the minor code status. The minor status code is stored here when the function returns to the function that called it:
OM_uint32 *minor_status ; /* status returned by mech */ major_status = gss_generic_function(&minor_status, arg1, arg2 ...);
The minor_status parameter is always set by a GSS-API routine, even if it returns a fatal major-code error, although most other output parameters can remain unset. However, output parameters that are expected to return pointers to storage allocated by the routine are set to NULL to indicate that no storage was actually allocated. Any length field associated with such pointers (as in a gss_buffer_desc structure) are set to zero. In these cases applications don't need to release these buffers.
GSS-API Tokens
The basic unit of currency, so to speak, in the GSS-API is the token. Applications using the GSS-API communicate with each other by using tokens, both for exchanging data and for making security arrangements. Tokens are declared as gss_buffer_t data types and are opaque to applications.
The two types of tokens are: context-level tokens and per-message tokens. Context-level tokens are used primarily when a context is established (initiated and accepted), although they can also be passed afterward to manage a context.
Per-message tokens are used after a context has been established, and are used to provide protection services on data. For example, if an application wants to send a message to another application, it might use the GSS-API to generate a cryptographic identifier to go along that message; that identifier would be stored in a token.
Per-message tokens can be considered with regard to "messages" as follows. A message is a piece of data that an application sends to a peer; for example, the ls command sent to an ftp server. A per-message token is an object generated by the GSS-API for that message, such as a cryptographic tag, or the encrypted form of the message. (Semantically speaking, this last example is mildly inaccurate: an encrypted message is still a message, not a token, since a token is only the GSS-API-generated information. However, informally, message and per-message token are often used interchangeably.)
It is the responsibility of the application (not the GSS-API) to:
Send and receive tokens. The developer usually needs to write generalized read and write functions for performing these actions. "send_token()" and "recv_token()" are examples of such functions.
Distinguish between types of tokens and manipulate them accordingly.
Because tokens are opaque to applications, there is no difference (to the application) between one token and another. Therefore, an application must be able to distinguish one token from another without explicitly knowing their contents, before passing them on to the appropriate GSS-API functions. The ways an application can distinguish tokens include:
By state -- that is, through the control-flow of a program. For example, if an application is waiting to accept a context, it can assume that any token it receives is a context-level token related to context-establishment, because it expects peers to wait until the context is fully established before sending message (data) tokens. After the context is established, the application can assume that any tokens it receives are message tokens. This is a fairly common way to handle tokens; the sample programs in this book use this method.
An application might distinguish types of tokens when sending and receiving them. For example, if the application has its own function for sending tokens to peers, it can include a flag indicating what kind of token is being sent:
Then the receiving application would have a receiving function (say, "get_a_token()") that would check the token_flag argument.gss_buffer_t token; /* declare the token */ OM_uint32 token_flag /* flag for describing the type of token */ <get token from a GSS-API function> token_flag = MIC_TOKEN; /* specify what kind of token it is */ send_a_token(&token, token_flag);
A third way might be through explicit tagging; for example, applications might use their own "meta-tokens": user-defined structures that contain tokens received from GSS-API functions, along with user-defined fields that signal how the GSS-API-provided tokens are to be used.
Interprocess Tokens
The GSS-API permits a security context to be passed from one process to another in a multiprocess application. Typically, this application has accepted a client's context and wants to share it among its processes. See "Context Export and Import" for information on multiprocess applications.
The gss_export_context() function creates an interprocess token that contains information allowing the context to be reconstituted by a second process. It is the responsibility of the application to pass this interprocess token from one process to the other, just as it is the application's responsibility to pass tokens to other applications.
Since this interprocess token might contain keys or other sensitive information, and since it cannot be guaranteed that all GSS-API implementations will cryptographically protect interprocess tokens, it is up to the application to protect them before exchange. This may involve encrypting them with gss_wrap(), if encryption is available.
Note - Interprocess tokens cannot be assumed to be transferable across different GSS-API implementations.
Programming Using the GSS-API
This section is designed to show, in general steps, how to implement secure data exchange using the GSS-API. It does not explain every GSS-API function. Instead, it concentrates on the half-dozen or so functions that are most central to using the GSS-API. For more information, see Appendix B, GSS-API Reference, which contains a list of all GSS-API functions (as well as GSS-API status codes and data types). Additionally, you can find out more about any GSS-API function by checking its man page.
To make things easier, this manual follows a simple model: A client application sends data to a remote server. The client does so directly -- that is, without mediation by transport protocol layers such as RPC. A set of sample programs (client and server) are shown in Appendix A, Sample C-Based GSS-API Programs. Chapter 2, A Walk-Through of the Sample GSS-API Programs takes you step-by-step through these programs.
Overview
These are the basic steps in using the GSS-API:
Each application, sender and recipient, acquires credentials explicitly, if credentials have not been acquired automatically.
The sender initiates a security context and the recipient accepts it.
The sender applies security protection to the message (data) it wants to transmit. This means that it either encrypts the message or stamps it with an identification tag. The sender transmits the protected message.
(The sender can choose not to apply either security protection, in which case the message has only the default GSS-API security service associated with it. That is authentication, in which the recipient knows that the sender is who it claims to be.)
The recipient decrypts the message (if needed) and verifies it (if appropriate).
(Optional) The recipient returns an identification tag to the sender for confirmation.
Both applications destroy the shared security context. If necessary, they can also deallocate any "leftover" GSS-API data.
Applications that use the GSS-API should include the file gssapi.h.