The authentication flavors mentioned previously -- AUTH_SYS, AUTH_DES, and AUTH_KERB -- can be overcome by a determined snoop. For this reason a new networking layer, the Generic Security Standard API, or GSS-API, has been added. The GSS-API framework offers two extra services beyond authentication:
Integrity. With the integrity service, the GSS-API uses the underlying mechanism to authenticate messages exchanged between programs. Cryptographic checksums establish:
The identity of the data originator to the recipient
The identity of the recipient to the originator (if mutual authentication is requested)
The authenticity of the transmitted data itself
Privacy. The privacy service includes the integrity service. In addition, the transmitted data is also encrypted so as to protect it from any eavesdroppers.
Due to U.S. export restrictions, the privacy service might not be available to all users.
Currently, the GSS-API is not exposed. Certain GSS-API features, however, are "visible" through RPCSEC_GSS functions -- they can be manipulated in an "opaque" fashion. The programmer need not be directly concerned with their values.
The RPCSEC_GSS security flavor allows ONC RPC applications to take advantage of the features of GSS-API. RPCSEC_GSS sits "on top" of the GSS-API layer as follows:
Using the programming interface for RPCSEC_GSS, ONC RPC applications can specify:
A security paradigm. Each kind of security mechanism offers a different kind of data protection, as well as one or more levels of data protection. In this case, any security mechanism supported by the GSS-API (Kerberos V5, RSA public key, and so forth).
Either privacy or integrity (or neither). The default is integrity The service is mechanism-independent.
Quality of Protection. The QOP specifies the type of cryptographic algorithm to be used to implement privacy or integrity services. Each security mechanism can have one or more QOPs associated with it.
Applications can obtain lists of valid QOPs and mechanisms through functions provided by RPCSEC_GSS. (See "Miscellaneous Functions".) Developers should avoid hard-coding mechanisms and QOPs into their applications, so that the applications will not need to be modified to use new or different mechanisms and QOPs.
Historically, "security flavor" and "authentication flavor" have meant the same thing. With the introduction of RPCSEC_GSS, "flavor" now has a somewhat different sense. A flavor can now include a service (integrity or privacy) along with authentication, although currently RPCSEC_GSS is the only flavor that does.
Using RPCSEC_GSS, ONC RPC applications establish a security context with a peer, exchange data, and destroy the context, just as they do with other flavors. Once a context is established, the application can change the QOP and service for each data unit sent.
For more information on RPCSEC_GSS, including RPCSEC_GSS data types, see the rpcsec_gss(3N) man page.
Table 4-8 summarizes RPCSEC_GSS commands. It is intended as a general overview of RPCSEC_GSS functions, rather than a specific description of each one. For more information on each function, see its man page, or check the rpcsec_gss(3N) man page for an overview, including a list of RPCSEC_GSS data structures.
Table 4-8 RPCSEC_GSS FunctionsAction | Function | Input | Output |
---|---|---|---|
Create a security context | rpc_gss_seccreate() | CLIENT handle, principal name, mechanism, QOP, service type | AUTH handle |
Change QOP, service type for context | rpc_gss_set_defaults() | Old QOP, service | New QOP, service |
Show maximum size for data before security transformation | rpc_gss_max_data_length() | Maximum data size allowed by transport | Maximum pre-transformation data size |
Show maximum size for data before security transformation | rpc_gss_svc_max_data_length() | Maximum data size allowed by transport | Maximum pre-transformation data size |
Set name of principal(s) for server to represent | rpc_gss_set_svc_name() | Principal name, RPC program, version #s | TRUE if successful |
Fetch credentials of caller (client) | rpc_gss_getcred() | Pointer to svc_req structure | UNIX credentials, RPCSEC_GSS credentials, cookie |
Specify (user-writen) callback function | rpc_gss_set_callback() | Pointer to callback function | TRUE if successful |
Create RPCSEC_GSS structure for principal names from unique parameters | rpc_gss_get_principal_name() | Mechanism, user name, machine name, domain name | RPCSEC_GSS principal name structure |
Fetch an error code when an RPCSEC_GSS routine fails | rpc_gss_get_error() |
| RPCSEC_GSS error number, errno if applicable |
Get strings for installed mechanisms | rpc_gss_get_mechanisms() |
| List of valid mechanisms |
Get valid QOP strings | rpc_gss_get_mech_info() | Mechanism | Valid QOPs for that mechanism |
Get the highest, lowest version numbers of RPCSEC_GSS supported | rpc_gss_get_versions() |
| Highest, lowest versions |
Check to see if a mechanism is installed | rpc_gss_is_installed() | Mechanism | TRUE if installed |
Convert ASCII mechanism to RPC object identifier | rpc_gss_mech_to_oid() | Mechanism (as string) | Mechanism (as OID) |
Convert ASCII QOP to integer | rpc_gss_qop_to_num() | QOP (as string) | QOP (as integer) |
Contexts are created with the rpc_gss_seccreate() call. This function takes as its arguments:
A client handle (returned, for example, by clnt_create())
The name of the server principal (for example, nfs@acme.com)
The mechanism (for example, Kerberos V5) for the session
The security service type (for example, privacy)
The QOP for the session
Two GSS-API parameters that can remain opaque for most uses (that is, the programmer can supply NULL values)
It returns an AUTH authentication handle. Example 4-29 shows how rpc_gss_seccreate() might be used to create a context using the Kerberos V5 security mechanism and the integrity service:
CLIENT *clnt; /* client handle */ char server_host[] = "foo"; char service_name[] = "nfs@eng.acme.com"; char mech[] = "kerberos_v5"; clnt = clnt_create(server_host, SERVER_PROG, SERV_VERS, "netpath"); clnt->clnt_auth = rpc_gss_seccreate(clnt, service_name, mech, rpc_gss_svc_integrity, NULL, NULL, NULL); . . .
A few things to note about Example 4-29 are:
Although the mechanism was declared explicitly (for ease of reading), it would be more commonly obtained programmatically with rpc_gss_get_mechanisms() from a table of available mechanisms.
The QOP is passed as a NULL, which sets the QOP to this mechanism's default. Otherwise, a valid value could, as with the mechanism, be obtained programmatically with rpc_gss_get_mechanisms(). See the rpc_gss_get_mechanisms(3N) man page for more information.
The security service type, rpc_gss_svc_integrity, is an enum of the RPCSEC_GSS type rpc_gss_service_t. rpc_gss_service_t has the following format:
typedef enum { rpc_gss_svc_default = 0, rpc_gss_svc_none = 1, rpc_gss_svc_integrity = 2, rpc_gss_svc_privacy = 3 } rpc_gss_service_t;
The default security service maps to integrity, so the programmer could have specified rpc_gss_svc_default and obtained the same result.
For more information, see the rpc_gss_seccreate(3N) man page.
Once a context has been set, the application may need to change QOP and service values for individual data units being transmitted. (For example, you might want a program to encrypt a password but not a login name.) rpc_gss_set_defaults() allows you to do so:
rpc_gss_set_defaults(clnt->clnt_auth, rpc_gss_svc_privacy, qop); . . .
In this case, the security service is set to privacy (see "Creating a Context"). qop is a pointer to a string naming the new QOP.
Contexts are destroyed in the usual way, with auth_destroy().
For more information on changing service and QOP, see the rpc_gss_set_defaults(3N) man page.
Two types of principal names are needed to establish and maintain a security context:
A server principal name. A server's principal name is always specified as a NULL-terminated ASCII string of the form service@host -- for example, nfs@eng.acme.com.
When a client creates a security context, it specifies the server principal name in this format (see "Creating a Context"). Similarly, when a server needs to set the name of a principal it will represent, it uses rpc_gss_set_svc_name(), which takes a principal name in this format as an argument.
A client principal name. The principal name of a client, as received by a server, takes the form of an rpc_gss_principal_t structure: a counted, opaque byte string determined by the mechanism being used. This structure is described on the rpcsec_gss(3N) man page.
A server needs to be told the names of the principals it will represent when it starts up. (A server may act as more than one principal.) rpc_gss_set_svc_name() sets the name of the principal(s):
char *principal, *mechanism; u_int req_time; principal = "nfs@eng.acme.com"; mechanism = "kerberos_v5"; req_time = 10000; /* time for which credential should be valid */ rpc_gss_set_svc_name(principal, mechanism, req_time, SERV_PROG, SERV_VERS);
(Kerberos ignores the req_time parameter. Other authentication systems may use it.)
For more information, see the rpc_gss_set_svc_name(3N) man page.
Servers need to be able to operate on a client's principal name -- for example, to compare a client's principal name to an access control list, or look up a UNIX credential for that client, if such a credential exists. Such principal names are kept in the form of a rpc_gss_principal_t structure pointer. (See the rpcsec_gss(3N) man page for more on rpc_gss_principal_t.) If a server wants to compare a principal name it has received with the name of a known entity, it needs to be able to generate a principal name in that form.
The rpc_gss_get_principal_name() call takes as input several parameters that uniquely identify an individual on a network, and generates a principal name as a rpc_gss_principal_t structure pointer:
rpc_gss_principal_t *principal; rpc_gss_get_principal_name(principal, mechanism, name, node, domain); . . .
The arguments to rpc_gss_get_principal_name() are as follows:
principal is a pointer to the rpc_gss_principal_t structure to be set.
mechanism is the security mechanism being used (remember, the principal name being generated is mechanism-dependent).
name is an individual or service name, such as joeh or nfs, or even the name of a user-defined application.
node might be, for example, a UNIX machine name.
domain might be, for example, a DNS, NIS, or NIS+ domain name, or a Kerberos realm.
Each security mechanism requires different identifying parameters. For example, Kerberos V5 requires a user name and, only optionally, qualified node and domain names (in Kerberos terms, host and realm names).
For more information, see the rpc_gss_get_principal_name(3N) man page.
Principal names are freed up using the free() library call.
A server must be able to fetch the credentials of a client. The rpc_gss_getcred() function, shown in Example 4-33, allows the server to retrieve either UNIX credentials or RPCSEC_GSS credentials (or both, for that matter). It does so through two arguments that are set if the function is successful. One is a pointer to an rpc_gss_ucred_t structure, which contains the caller's UNIX credentials, if such exist:
typedef struct { uid_t uid; /* user ID */ gid_t gid; /* group ID */ short gidlen; git_t *gidlist; /* list of groups */ } rpc_gss_ucred_t;
The other argument is a pointer to a rpc_gss_raw_cred_t structure, which looks like this:
typedef struct { u_int version; /* RPCSEC_GSS program version */ char *mechanism; char *qop; rpc_gss_principal_t client_principal; /* client principal name */ char *svc_principal; /* server principal name */ rpc_gss_service_t service; /* privacy, integrity enum */ } rpc_gss_rawcred_t;(See "Generating Client Principal Names" for a description of the rpc_gss_principal_t structure and how it is created.) Because rpc_gss_rawcred_t contains both the client and server principal names, rpc_gss_getcred() can return them both.
Example 4-33 is an example of a simple server-side dispatch procedure, in which the server gets the credentials for the caller. The procedure gets the caller's UNIX credentials and then verifies the user's identity, using the mechanism, QOP, and service type found in the rpc_gss_rcred_t argument.
static void server_prog(struct svc_req *rqstp, SVCXPRT *xprt) { rpc_gss_ucred_t *ucred; rpc_gss_rawcred_t *rcred; if (rqst->rq_proq == NULLPROC) { svc_sendreply(xprt, xdr_void, NULL); return; } /* * authenticate all other requests */ */ switch (rqstp->rq_cred.oa_flavor) { case RPCSEC_GSS: /* * get credential information */ rpc_gss_getcred(rqstp, &rcred, &ucred, NULL); /* * verify that the user is allowed to access * using received security parameters by * peeking into my config file */ if (!authenticate_user(ucred->uid, rcred->mechanism, rcred->qop, rcred->service)) { svcerr_weakauth(xprt); return; } break; /* allow the user in */ default: svcerr_weakauth(xprt); return; } /* end switch */ switch (rqstp->rq_proq) { case SERV_PROC1: . . . } /* usual request processing; send response ... */ return; } |
For more information, see the rpc_gss_getcred(3N) man page.
In Example 4-33, the last argument to rpc_gss_getcred() (here, a NULL) is a user-defined cookie, whose value on return will be whatever was specified by the server when the context was created. This cookie, a four-byte value, can be used in any way appropriate for the application -- RPC does not interpret it. For example, the cookie can be a pointer or index to a structure that represents the context initiator; instead of computing this value for every request, the server computes it at context-creation time (thus saving on request-processing time).
Another place where cookies can be used is with callbacks. A server can specify a (user-defined) callback so that it knows when a context first gets used, by using the rpc_gss_set_callback() function. The callback is invoked the first time a context is used for data exchanges, after the context is established for the specified program and version.
The user-defined callback routine takes the following form:
The second and third arguments, deleg and gss_context, are GSS-API data types and are not currently exposed, so the callback function can ignore them. (Briefly, deleg is the identity of any delegated peer, while gss_context is a pointer to the GSS-API context, in case the program wanted to perform GSS-API operations on the context -- that is, to test for acceptance criteria.) The cookie argument we have already seen.
The lock argument is a pointer to a rpc_gss_lock_t structure:
typedef struct { bool_t locked; rpc_gss_rawcred_t *raw_cred; } rpc_gss_lock_t;This parameter enables a server to enforce a particular QOP and service for the session. QOP and service are found in the rpc_gss_rawcred_t structure described in Example 4-33. (A server should not change the values for service and QOP.) When the user-defined callback is invoked, the locked field is set to FALSE. If the server sets locked to TRUE, only requests with QOP and service values that match the QOP and service values in the rpc_gss_rawcred_t structure will be accepted.
For more information, see the rpc_gss_set_callback(3N) man page.
Two functions -- rpc_gss_max_data_length() and rpc_gss_svc_max_data_length() -- are useful in determining how large a piece of data can be before it is transformed by security measures and sent "over the wire." That is, a security transformation such as encryption usually changes the size of a piece of transmitted data (most often enlarging it). To make sure that data won't be enlarged past a usable size, these two functions -- the former is the client-side version, the latter the server-side -- return the maximum pre-transformation size for a given transport.
For more information, see the rpc_gss_max_data_length(3N) and rpc_gss_svc_max_data_length(3N) man pages.
Several functions are useful for getting information about the installed security system:
rpc_gss_get_mechanisms() returns a list of installed security mechanisms
rpc_gss_is_installed() checks to see if a specified mechanism is installed
rpc_gss_get_mech_info() returns valid QOPs for a given mechanism
Using these functions gives the programmer latitude in avoiding hard-coding security parameters in applications. (See Table 4-8 and the rpcsec_gss(3N) man page for a list of all RPCSEC_GSS functions.)
RPCSEC_GSS makes use of certain files to store information.
When a server retrieves the client credentials associated with a request, it can get either the client's principal name (in the form of a rpc_gss_principal_t structure pointer) or local UNIX credentials (UID) for that client. Services such as NFS require a local UNIX credential for access checking, but others might not; they can, for example, store the principal name, as a rpc_gss_principal_t structure, directly in their own access control lists.
The correspondence between a client's network credential (its principal name) and any local UNIX credential is not automatic -- it must be set up explicitly by the local security administrator.
The gsscred file contains both the client's UNIX and network (for example, Kerberos V5) credentials. (The latter is the Hex-ASCII representation of the rpc_gss_principal_t structure.) It is accessed through XFN; thus, this table can be implemented over files, NIS, or NIS+, or any future name service supported by XFN. In the XFN hierarchy, this table appears as this_org_unit/service/gsscred. The gsscred table is maintained with the use of the gsscred utility, which allows administrators to add and delete users and mechanisms.
For convenience, RPCSEC_GSS uses string literals for representing mechanisms and Quality of Protection (QOP) parameters. The underlying mechanisms themselves, however, require mechanisms to be represented as object identifiers and QOPs as 32-bit integers. Additionally, for each mechanism, the shared library that implements the services for that mechanism needs to be specified.
The /etc/gss/mech file stores the following information on all installed mechanisms on a system: the mechanism name, in ASCII; the mechanism's OID; the shared library implementing the services provided by this mechanism; and, optionally, the kernel module implementing the service. A sample line might look like this:
kerberos_v5 1.2.840.113554.1.2.2 gl/mech_krb5.so gl_kmech_krb5 |
The /etc/gss/qop file stores, for all mechanisms installed, all the QOPs supported by each mechanism, both as an ASCII string as its corresponding 32-bit integer.
Both /etc/gss/mech and /etc/gss/qop are created when security mechanisms are first installed on a given system.
Because many of the in-kernel RPC routines use non-string values to represent mechanism and QOP, applications can use the rpc_gss_mech_to_oid() and rpc_gss_qop_to_num() functions to get the non-string equivalents for these parameters, should they need to take advantage of those in-kernel routines.