Skip Navigation Links | |
Exit Print View | |
Developer's Guide to Oracle Solaris 11 Security Oracle Solaris 11.1 Information Library |
1. Oracle Solaris Security for Developers (Overview)
2. Developing Privileged Applications
3. Writing PAM Applications and Services
4. Writing Applications That Use GSS-API
Application Portability With GSS-API
Available Mechanisms in GSS-API
Remote Procedure Calls With GSS-API
Where to Get More Information on GSS-API
Developing Applications That Use GSS-API
Working With Credentials in GSS-API
Acquiring Credentials in GSS-API
Working With Contexts in GSS-API
Initiating a Context in GSS-API
Accepting a Context in GSS-API
Using Other Context Services in GSS-API
Delegating a Credential in GSS-API
Performing Mutual Authentication Between Peers in GSS-API
Performing Anonymous Authentication in GSS-API
Using Channel Bindings in GSS-API
Exporting and Importing Contexts in GSS-API
Obtaining Context Information in GSS-API
Sending Protected Data in GSS-API
Tagging Messages With gss_get_mic()
Wrapping Messages With gss_wrap()
Handling Wrap Size Issues in GSS-API
Detecting Sequence Problems in GSS-API
Confirming Message Transmission in GSS-API
7. Writing Applications That Use SASL
8. Introduction to the Oracle Solaris Cryptographic Framework
9. Writing User-Level Cryptographic Applications
10. Introduction to the Oracle Solaris Key Management Framework
A. Secure Coding Guidelines for Developers
B. Sample C-Based GSS-API Programs
This section covers the following important GSS-API concepts: principals, GSS-API data types, status codes, and tokens.
The following sections explain the major GSS-API data types. For information on all GSS-API data types, see GSS-API Data Types and Values.
Because the size of an int can vary from platform to platform, GSS-API provides the following integer data type:OM_uint32which is a 32–bit unsigned integer.
Because GSS-API handles all data in internal formats, strings must be converted to a GSS-API format before being passed to GSS-API functions. GSS-API handles strings with the gss_buffer_desc structure:
typedef struct gss_buffer_desc_struct { size_t length; void *value; } gss_buffer_desc *gss_buffer_t;
gss_buffer_t is a pointer to such a structure. Strings must be put into a gss_buffer_desc structure before being passed to functions that use them. In the following example, a generic GSS-API function applies protection to a message before sending that message.
Example 4-1 Using Strings in GSS-API
char *message_string; gss_buffer_desc input_msg_buffer; input_msg_buffer.value = message_string; input_msg_buffer.length = strlen(input_msg_buffer.value) + 1; gss_generic_function(arg1, &input_msg_buffer, arg2...); gss_release_buffer(input_msg_buffer);
Note that input_msg_buffer must be deallocated with gss_release_buffer() when you are finished with input_msg_buffer.
The gss_buffer_desc object is not just for character strings. For example, tokens are manipulated as gss_buffer_desc objects. See GSS-API Tokens for more information.
A name refers to a principal. In network-security terminology, a principal is a user, a program, or a machine. Principals can be either clients or servers.
Some examples of principals are:
A user, such as user@machine, who logs into another machine
A network service, such as nfs@machine
A machine, such as myHost@eng.company.com, that runs an application
In GSS-API, names are stored as a gss_name_t object, which is opaque to the application. Names are converted from gss_buffer_t objects to the gss_name_t form by the gss_import_name() function. Every imported name has an associated name type, which indicates the format of the name. See GSS-API OIDs for more about name types. See Name Types for a list of valid name types.
gss_import_name() has the following syntax:
OM_uint32 gss_import_name ( OM_uint32 *minor-status, const gss_buffer_t input-name-buffer, const gss_OID input-name-type, gss_name_t *output-name)
Status code returned by the underlying mechanism. See GSS-API Status Codes.
The gss_buffer_desc structure containing the name to be imported. The application must allocate this structure explicitly. See Strings and Similar Data in GSS-API as well as Example 4-2. This argument must be deallocated with gss_release_buffer() when the application is finished with the space.
A gss_OID that specifies the format of input-name-buffer. See Name Types in GSS-API. Also, Name Types contains a table of valid name types.
The gss_name_t structure to receive the name.
A minor modification of the generic example shown in Example 4-1 illustrates how gss_import_name() can be used. First, the regular string is inserted into a gss_buffer_desc structure. Then gss_import_name() places the string into a gss_name_t structure.
Example 4-2 Using gss_import_name()
char *name_string; gss_buffer_desc input_name_buffer; gss_name_t output_name_buffer; input_name_buffer.value = name_string; input_name_buffer.length = strlen(input_name_buffer.value) + 1; gss_import_name(&minor_status, input_name_buffer, GSS_C_NT_HOSTBASED_SERVICE, &output_name); gss_release_buffer(input_name_buffer);
An imported name can be put back into a gss_buffer_t object for display in human-readable form with gss_display_name(). However, gss_display_name() does not guarantee that the resulting string will be the same as the original due to the way the underlying mechanisms store names. GSS-API includes several other functions for manipulating names. See GSS-API Functions.
A gss_name_t structure can contain several versions of a single name. One version is produced for each mechanism that is supported by GSS-API. That is, a gss_name_t structure for user@company might contain one version of that name as rendered by Kerberos v5 and another version that was given by a different mechanism. The function gss_canonicalize_name() takes as input an internal name and a mechanism. gss_canonicalize_name() yields a second internal name that contains a single version of the name that is specific to that mechanism.
Such a mechanism-specific name is called a mechanism name (MN). A mechanism name does not refer to the name of a mechanism, but to the name of a principal as produced by a given mechanism. This process is illustrated in the following figure.
Figure 4-3 Internal Names and Mechanism Names
Consider the case where a server has received a name from a client and needs to look up that name in an access control list. An access control list, or ACL, is a list of principals with particular access permissions.
One way to do the lookup would be as follows:
Import the client name into GSS-API internal format with gss_import_name(), if the name has not already been imported.
In some cases, the server will receive a name in internal format, so this step will not be necessary. For example, a server might look up the client's own name. During context initiation, the client's own name is passed in internal format.
Import each name in the ACL with gss_import_name().
Compare each imported ACL name with the imported client's name, using gss_compare_name().
This process is shown in the following figure. In this case, Step 1 is assumed to be needed.
Figure 4-4 Comparing Names (Slow)
The previous approach of comparing names individually is acceptable when there are only a few names. When there are a large number of names, using the gss_canonicalize_name() function is more efficient.
This approach uses the following steps:
Import the client's name with gss_import_name(), if the name has not already been imported.
As with the previous method of comparing names, if the name is already in internal format, this step is unnecessary.
Use gss_canonicalize_name() to produce a mechanism name version of the client's name.
Use gss_export_name() to produce an exported name, which is the client's name as a contiguous string.
Compare the exported client's name with each name in the ACL by using memcmp(), which is a fast, low-overhead function.
This process is shown in the following figure. Again, assume that the server needs to import the name that is received from the client.
Figure 4-5 Comparing Names (Fast)
Because gss_export_name() expects a mechanism name (MN), you must run gss_canonicalize_name() on the client's name first.
See the gss_export_name(3GSS), gss_import_name(3GSS), and gss_canonicalize_name(3GSS) for more information.
Object identifiers (OIDs) are used to store the following kinds of data:
OIDs are stored in GSS-API gss_OID_desc structure. GSS-API provides a pointer to the structure, gss_OID, as shown in the following example.
Example 4-3 OIDs Structure
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 4-4 OID Set Structure
typedef struct gss_OID_set_desc_struct { size_t count; gss_OID elements; } gss_OID_set_desc, *gss_OID_set;
Although GSS-API allows applications to choose underlying security mechanisms, applications should use the default mechanism that has been selected by GSS-API if possible. Similarly, although GSS-API lets an application specify a Quality of Protection level for protecting data, the default QOP should be used if possible. Acceptance of the default mechanism is indicated 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 defeats the purpose of using GSS-API. Such a specific selection limits the portability of an application. Other implementations of GSS-API might not support that QOP or mechanism in the intended manner. Nonetheless, Appendix D, Specifying an OID briefly discusses how to find out which mechanisms and QOPs are available, and how to choose one. |
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. If the name type is GSS_C_NT_EXPORT_NAME, then the function expects 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 GSS-API is provided in Name Types.
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
Major status codes indicate the following errors:
Generic GSS-API routine errors, such as giving a routine an invalid mechanism
Call errors that are specific to a particular GSS-API language binding, such as a function argument that cannot be read, cannot be written, or is malformed
Both types of errors
Additionally, major status codes can provide supplementary information about a routine's status. For example, a code might indicate that an operation is not finished, 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 follows:
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, consider the following code.
OM_uint32 maj_stat; maj_sta = gss_generic_function(arg1, arg2 ...); if (maj_stat == GSS_CREDENTIALS_EXPIRED) <do something...>
Major status codes 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
Minor status codes are returned by the underlying mechanism. These codes are not specifically documented in this manual.
Every GSS-API function has as a first argument an OM_uint32 type for the minor code status. The minor status code is stored in the OM_uint32 argument when the function returns to the calling function. Consider the following code.
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 a fatal major status code error is returned. Note that most other output parameters can remain unset. However, output parameters that are expected to return pointers to storage that has been allocated by the routine are set to NULL. NULL indicates 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 such cases, applications do not need to release these buffers.
The basic unit of “currency” in GSS-API is the token. Applications that use GSS-API communicate with each other by using tokens. Tokens are used for exchanging data and for making security arrangements. Tokens are declared as gss_buffer_t data types. Tokens are opaque to applications.
Two types of tokens are context-level tokens and per-message tokens. Context-level tokens are used primarily when a context is established, that is, initiated and accepted. Context-level tokens can also be passed afterward to manage a context.
Per-message tokens are used after a context has been established. Per-message tokens are used to provide protection services on data. For example, consider an application that wants to send a message to another application. That application might use GSS-API to generate a cryptographic identifier to go along with that message. The 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 could be a message that is sent to an ftp server. A per-message token is an object generated by GSS-API for that message. A per-message token could be a cryptographic tag or the encrypted form of the message. Note that this last example is mildly inaccurate. An encrypted message is still a message and not a token. A token is only GSS-API-generated information. However, informally, message and per-message token are often used interchangeably.
An application is responsible for the following activities:
Sending and receiving tokens. The developer usually needs to write generalized read and write functions for performing these actions. The send_token() and recv_token() functions in Miscellaneous GSS-API Sample Functions.
Distinguishing between types of tokens and manipulating the tokens accordingly.
Because tokens are opaque to applications, the application does not distinguish between one token and another. Without knowing a token's contents, an application must be able to distinguish the token's type to pass that token to an appropriate GSS-API function.
An application can distinguish token types through the following methods:
By state. Through the control-flow of a program. For example, an application that is waiting to accept a context might assume that any received tokens are related to context establishment. Peers are expected to wait until the context is fully established before sending message tokens, that is, data. After the context is established, the application assumes that new tokens are message tokens. This approach to handling tokens is a fairly common way to handle tokens. The sample programs in this book use this method.
By flags. For example, if an application has a function for sending tokens to peers, that application can include a flag to indicate the kind of token. Consider the following code:
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);
The receiving application would have a receiving function, for example, get_a_token(), that would check the token_flag argument.
Through explicit tagging. Applications can use meta-tokens. A meta-token is a user-defined structure that contain tokens that have been received from GSS-API functions. A meta-token includes user-defined fields that signal how the tokens that are provided by GSS-API are to be used.
GSS-API permits a security context to be passed from one process to another in a multiprocess application. Typically, a application has accepted a client's context. The application then shares the context among that application's processes. See Exporting and Importing Contexts in GSS-API for information on multiprocess applications.
The gss_export_context() function creates an interprocess token. This token contains information that enables the context to be reconstituted by a second process. The application is responsible for passing the interprocess token from one process to the other. This situation is similar to the application's responsibility for passing tokens to other applications.
The interprocess token might contain keys or other sensitive information. Not all GSS-API implementations cryptographically protect interprocess tokens. Therefore, the application must protect interprocess tokens before an exchange takes place. This protection might involve encrypting the tokens with gss_wrap(), if encryption is available.
Note - Do not assume that interprocess tokens are transferable across different GSS-API implementations.