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
GSSAPI Server Example Overview
GSSAPI Server Example Structure
Running the GSSAPI Server Example
GSSAPI Server Example: main() Function
Cleanup in the GSSAPI Server Example
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
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:
Accepts the context
Unwraps the data
Signs the data
Returns the data
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 Oracle download center. See http://www.oracle.com/technetwork/indexes/downloads/sdlc-decommission-333274.html.
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); }
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 Oracle download center. See http://www.oracle.com/technetwork/indexes/downloads/sdlc-decommission-333274.html.
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 */
min_stat is the error status returned by the underlying mechanism.
context is the context being established.
server_creds is the credential for the service to be provided (see Acquiring Credentials).
recv_tok is the token received from the client by recv_token().
GSS_C_NO_CHANNEL_BINDINGS is a flag indicating not to use channel bindings (see Using Channel Bindings in GSS-API).
client is the ASCII name of the client.
oid is the mechanism (in OID format).
send_tok is the token to send to the client.
ret_flags are various flags indicating whether the context supports a given option, such as message-sequence-detection.
The two NULL arguments indicate that the program does not need to know the length of time that the context will be valid, or whether the server can act as a client's proxy.
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); }
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.
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().
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 Oracle download center. See http://www.oracle.com/technetwork/indexes/downloads/sdlc-decommission-333274.html.
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; }