Sending the Data
Having established the security context, gss-client needs to wrap the data, send it, and then verify the "signature" that the server returns. Because gss-client is an example program, it does various other things as well, such as display information about the context, but we'll skip all of that in order to get the data sent out and verified. So first the program puts the message to be sent (such as "ls") into a buffer:
if (use_file) { read_file(msg, &in_buf); } else { /* Wrap the message */ in_buf.value = msg; in_buf.length = strlen(msg) + 1; } |
Before wrapping, the program checks to see if it can encrypt the data:
if (ret_flag & GSS_C_CONF_FLAG) { state = 1; else state = 0; } |
And then it wraps it up:
maj_stat = gss_wrap(&min_stat, context, conf_req_flag, GSS_C_QOP_DEFAULT, &in_buf, &state, &out_buf); if (maj_stat != GSS_S_COMPLETE) { display_status("wrapping message", maj_stat, min_stat); (void) close(s); (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER); return -1; } else if (! state) { fprintf(stderr, "Warning! Message not encrypted.\n"); } |
Thus the message stored in in_buf is to be sent to the server referenced by context, with confidentiality service and the default Quality of Protection (QOP) requested. (Quality of Protection indicates which algorithm to apply in transforming the data; it's a good idea for portability's sake to use the default whenever possible.) gss_wrap() wraps the message, puts the result into out_buf, and sets a flag (state) that indicates whether confidentiality was in fact applied in the wrapping.
The client sends the wrapped message to the server with its own send_token() function, which you've already seen in "Establishing a Context":
send_token(s, &outbuf) |
Verifying the Message
The program can now verify the validity of the message it sent. It knows that the server returns the MIC for the message it sent, so it retrieves it with its recv_token() function and then uses gss_verify_mic() to verify its "signature" (the MIC).
maj_stat = gss_verify_mic(&min_stat, context, &in_buf, &out_buf, &qop_state); if (maj_stat != GSS_S_COMPLETE) { display_status("verifying signature", maj_stat, min_stat); (void) close(s); (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER); return -1; } |
gss_verify_mic() compares the MIC received with the server's token (in out_buf) with one it produces from the original, unwrapped message, held in in_buf. If the two MICs match, the message is verified. The client releases the buffer for the received token, out_buf.
To finish, call_server() deletes the context and returns to main().
Server-Side GSS-API: gss-server
Naturally, the client needs a server to perform a security handshake. Where the client initiates a security context and sends data, the server must accept the context, verifying the identity of the client. In doing so, it might need to authenticate itself to the client, if requested to do so, and it may have to provide a "signature" for the data to the client. Plus, of course, it has to process the data!
gss-server takes this form on the command line (the line has been broken up to make it fit):
gss-server [-port port] [-verbose] [-inetd] [-once] [-logfile file] \ [-mech mechanism] service_name |
gss-server does the following:
Parses the command line.
Translates the mechanism name given on the command-line, if any, to internal format.
Acquires credentials for the caller.
Checks to see if the user has specified using the inetd daemon for connecting or not.
Establishes a connection.
Gets the data.
Signs the data and returns it.
Releases namespaces and exits.
Following is a step-by-step description of how gss-server works. Because it is a sample program designed to show off functionality, the parts of the program that do not closely relate to the steps above are skipped here.
Overview: main() (Server)
gss-client begins with the main() function. main() performs the following tasks:
It parses command-line arguments, assigning them to variables:
port, if specified, is the port number to listen on. If no port is specified, the program uses port 4444 as the default.
If -verbose is specified, the program runs in a quasi-debug mode.
The -inetd option indicates that the program should use the inetd daemon to listen to a port; inetd uses stdin and stdout to hand the connection to the client.
If -once is specified, then the program makes only a single-instance connection.
mechanism is the (optional) name of the security mechanism to use, such as Kerberos v5, to use. If no mechanism is specified, the GSS-API uses a default mechanism.
The name of the network service requested by the client (such as telnet, ftp, or login service) is specified by service_name.
An example command line might look like this:
% gss-server -port 8080 -once -mech kerberos_v5 erebos.eng nfs "hello"
It converts the mechanism, if specified, to a GSS-API object identifier (OID). This is because GSS-API functions handle names in internal format.
It acquires the credentials for the service (such as ftp), for the mechanism being used (for example, Kerberos v5).
It calls the sign_server() function, which does most of the work (establishes the connection, retrieves and signs the message, and so on).
If the user has specified using inetd, then the program closes the standard output and standard error and calls sign_server() on the standard input, which inetd uses to pass connections. Otherwise, it creates a socket, accepts the connection for that socket with the TCP function accept(), and calls sign_server() on the file descriptor returned by accept().
If inetd is not used, the program creates connections and contexts until it's terminated. However, if the user has specified the -once option, the loop terminates after the first connection.
It releases the credentials it has acquired.
It releases the mechanism OID namespace.
It closes the connection, if it's still open.