/* * Copyright (c) 2010, Oracle America, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the "Oracle America, Inc." nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * svcauth_des.c, server-side des authentication * * We insure for the service the following: * (1) The timestamp microseconds do not exceed 1 million. * (2) The timestamp plus the window is less than the current time. * (3) The timestamp is not less than the one previously * seen in the current session. * * It is up to the server to determine if the window size is * too small . * */ #include #include #include #include #include #include #include #include #include #include #include #include #define debug(msg) /*printf("svcauth_des: %s\n", msg) */ #define USEC_PER_SEC ((uint32_t) 1000000L) #define BEFORE(t1, t2) timercmp(t1, t2, <) /* * LRU cache of conversation keys and some other useful items. */ #define AUTHDES_CACHESZ 64 struct cache_entry { des_block key; /* conversation key */ char *rname; /* client's name */ u_int window; /* credential lifetime window */ struct rpc_timeval laststamp; /* detect replays of creds */ char *localcred; /* generic local credential */ }; #ifdef _RPC_THREAD_SAFE_ #define authdes_cache RPC_THREAD_VARIABLE(authdes_cache_s) #define authdes_lru RPC_THREAD_VARIABLE(authdes_lru_s) #else static struct cache_entry *authdes_cache; static int *authdes_lru; #endif static void cache_init (void) internal_function; /* initialize the cache */ static short cache_spot (des_block *, char *, struct rpc_timeval *) internal_function; /* find an entry in the cache */ static void cache_ref (uint32_t sid) internal_function; /* note that sid was ref'd */ static void invalidate (char *cred) internal_function; /* invalidate entry in cache */ /* * cache statistics */ struct { u_long ncachehits; /* times cache hit, and is not replay */ u_long ncachereplays; /* times cache hit, and is replay */ u_long ncachemisses; /* times cache missed */ } svcauthdes_stats; /* * Service side authenticator for AUTH_DES */ enum auth_stat _svcauth_des (register struct svc_req *rqst, register struct rpc_msg *msg) { register uint32_t *ixdr; des_block cryptbuf[2]; register struct authdes_cred *cred; struct authdes_verf verf; int status; register struct cache_entry *entry; uint32_t sid = 0; des_block *sessionkey; des_block ivec; u_int window; struct rpc_timeval timestamp; uint32_t namelen; struct area { struct authdes_cred area_cred; char area_netname[MAXNETNAMELEN + 1]; } *area; if (authdes_cache == NULL) cache_init (); if (authdes_cache == NULL) /* No free memory */ return AUTH_FAILED; area = (struct area *) rqst->rq_clntcred; cred = (struct authdes_cred *) &area->area_cred; /* * Get the credential */ if (msg->rm_call.cb_cred.oa_length <= 0 || msg->rm_call.cb_cred.oa_length > MAX_AUTH_BYTES) return AUTH_BADCRED; ixdr = (uint32_t *) msg->rm_call.cb_cred.oa_base; cred->adc_namekind = IXDR_GET_ENUM (ixdr, enum authdes_namekind); switch (cred->adc_namekind) { case ADN_FULLNAME: namelen = IXDR_GET_U_INT32 (ixdr); if (namelen > MAXNETNAMELEN) { return AUTH_BADCRED; } cred->adc_fullname.name = area->area_netname; memcpy (cred->adc_fullname.name, (char *) ixdr, namelen); cred->adc_fullname.name[namelen] = 0; ixdr += (RNDUP (namelen) / BYTES_PER_XDR_UNIT); cred->adc_fullname.key.key.high = *ixdr++; cred->adc_fullname.key.key.low = *ixdr++; cred->adc_fullname.window = *ixdr++; break; case ADN_NICKNAME: cred->adc_nickname = *ixdr++; break; default: return AUTH_BADCRED; } /* * Get the verifier */ if (msg->rm_call.cb_verf.oa_length <= 0 || msg->rm_call.cb_verf.oa_length > MAX_AUTH_BYTES) return AUTH_BADCRED; ixdr = (uint32_t *) msg->rm_call.cb_verf.oa_base; verf.adv_xtimestamp.key.high = *ixdr++; verf.adv_xtimestamp.key.low = *ixdr++; verf.adv_int_u = *ixdr++; /* * Get the conversation key */ if (cred->adc_namekind == ADN_FULLNAME) { netobj pkey; char pkey_data[1024]; sessionkey = &cred->adc_fullname.key; if (!getpublickey (cred->adc_fullname.name, pkey_data)) { debug("getpublickey"); return AUTH_BADCRED; } pkey.n_bytes = pkey_data; pkey.n_len = strlen (pkey_data) + 1; if (key_decryptsession_pk (cred->adc_fullname.name, &pkey, sessionkey) < 0) { debug ("decryptsessionkey"); return AUTH_BADCRED; /* key not found */ } } else { /* ADN_NICKNAME */ if (cred->adc_nickname >= AUTHDES_CACHESZ) { debug ("bad nickname"); return AUTH_BADCRED; /* garbled credential */ } else sid = cred->adc_nickname; /* XXX This could be wrong, but else we have a security problem */ if (authdes_cache[sid].rname == NULL) return AUTH_BADCRED; sessionkey = &authdes_cache[sid].key; } /* * Decrypt the timestamp */ cryptbuf[0] = verf.adv_xtimestamp; if (cred->adc_namekind == ADN_FULLNAME) { cryptbuf[1].key.high = cred->adc_fullname.window; cryptbuf[1].key.low = verf.adv_winverf; ivec.key.high = ivec.key.low = 0; status = cbc_crypt ((char *) sessionkey, (char *) cryptbuf, 2 * sizeof (des_block), DES_DECRYPT | DES_HW, (char *) &ivec); } else status = ecb_crypt ((char *) sessionkey, (char *) cryptbuf, sizeof (des_block), DES_DECRYPT | DES_HW); if (DES_FAILED (status)) { debug ("decryption failure"); return AUTH_FAILED; /* system error */ } /* * XDR the decrypted timestamp */ ixdr = (uint32_t *) cryptbuf; timestamp.tv_sec = IXDR_GET_INT32 (ixdr); timestamp.tv_usec = IXDR_GET_INT32 (ixdr); /* * Check for valid credentials and verifiers. * They could be invalid because the key was flushed * out of the cache, and so a new session should begin. * Be sure and send AUTH_REJECTED{CRED, VERF} if this is the case. */ { struct timeval current; int nick; u_int winverf; if (cred->adc_namekind == ADN_FULLNAME) { short tmp_spot; window = IXDR_GET_U_INT32 (ixdr); winverf = IXDR_GET_U_INT32 (ixdr); if (winverf != window - 1) { debug ("window verifier mismatch"); return AUTH_BADCRED; /* garbled credential */ } tmp_spot = cache_spot (sessionkey, cred->adc_fullname.name, ×tamp); if (tmp_spot < 0 || tmp_spot > AUTHDES_CACHESZ) { debug ("replayed credential"); return AUTH_REJECTEDCRED; /* replay */ } sid = tmp_spot; nick = 0; } else { /* ADN_NICKNAME */ window = authdes_cache[sid].window; nick = 1; } if (timestamp.tv_usec >= USEC_PER_SEC) { debug ("invalid usecs"); /* cached out (bad key), or garbled verifier */ return nick ? AUTH_REJECTEDVERF : AUTH_BADVERF; } if (nick && BEFORE (×tamp, &authdes_cache[sid].laststamp)) { debug ("timestamp before last seen"); return AUTH_REJECTEDVERF; /* replay */ } __gettimeofday (¤t, (struct timezone *) NULL); current.tv_sec -= window; /* allow for expiration */ if (!BEFORE (¤t, ×tamp)) { debug ("timestamp expired"); /* replay, or garbled credential */ return nick ? AUTH_REJECTEDVERF : AUTH_BADCRED; } } /* * Set up the reply verifier */ verf.adv_nickname = sid; /* * xdr the timestamp before encrypting */ ixdr = (uint32_t *) cryptbuf; IXDR_PUT_INT32 (ixdr, timestamp.tv_sec - 1); IXDR_PUT_INT32 (ixdr, timestamp.tv_usec); /* * encrypt the timestamp */ status = ecb_crypt ((char *) sessionkey, (char *) cryptbuf, sizeof (des_block), DES_ENCRYPT | DES_HW); if (DES_FAILED (status)) { debug ("encryption failure"); return AUTH_FAILED; /* system error */ } verf.adv_xtimestamp = cryptbuf[0]; /* * Serialize the reply verifier, and update rqst */ ixdr = (uint32_t *) msg->rm_call.cb_verf.oa_base; *ixdr++ = verf.adv_xtimestamp.key.high; *ixdr++ = verf.adv_xtimestamp.key.low; *ixdr++ = verf.adv_int_u; rqst->rq_xprt->xp_verf.oa_flavor = AUTH_DES; rqst->rq_xprt->xp_verf.oa_base = msg->rm_call.cb_verf.oa_base; rqst->rq_xprt->xp_verf.oa_length = (char *) ixdr - msg->rm_call.cb_verf.oa_base; /* * We succeeded, commit the data to the cache now and * finish cooking the credential. */ entry = &authdes_cache[sid]; entry->laststamp = timestamp; cache_ref (sid); if (cred->adc_namekind == ADN_FULLNAME) { size_t full_len; cred->adc_fullname.window = window; cred->adc_nickname = sid; /* save nickname */ if (entry->rname != NULL) mem_free (entry->rname, strlen (entry->rname) + 1); full_len = strlen (cred->adc_fullname.name) + 1; entry->rname = mem_alloc ((u_int) full_len); if (entry->rname != NULL) memcpy (entry->rname, cred->adc_fullname.name, full_len); else { debug ("out of memory"); return AUTH_FAILED; /* out of memory is bad */ } entry->key = *sessionkey; entry->window = window; invalidate (entry->localcred); /* mark any cached cred invalid */ } else { /* ADN_NICKNAME */ /* * nicknames are cooked into fullnames */ cred->adc_namekind = ADN_FULLNAME; cred->adc_fullname.name = entry->rname; cred->adc_fullname.key = entry->key; cred->adc_fullname.window = entry->window; } return AUTH_OK; /* we made it! */ } /* * Initialize the cache */ static void internal_function cache_init (void) { register int i; authdes_cache = (struct cache_entry *) calloc (sizeof (struct cache_entry) * AUTHDES_CACHESZ, 1); if (authdes_cache == NULL) return; authdes_lru = (int *) mem_alloc (sizeof (int) * AUTHDES_CACHESZ); /* * Initialize the lru list */ for (i = 0; i < AUTHDES_CACHESZ; ++i) authdes_lru[i] = i; } /* * Find the lru victim */ static short cache_victim (void) { return authdes_lru[AUTHDES_CACHESZ - 1]; } /* * Note that sid was referenced */ static void internal_function cache_ref (register uint32_t sid) { register int i; register int curr; register int prev; prev = authdes_lru[0]; authdes_lru[0] = sid; for (i = 1; prev != sid; ++i) { curr = authdes_lru[i]; authdes_lru[i] = prev; prev = curr; } } /* * Find a spot in the cache for a credential containing * the items given. Return -1 if a replay is detected, otherwise * return the spot in the cache. */ static short internal_function cache_spot (register des_block *key, char *name, struct rpc_timeval *timestamp) { register struct cache_entry *cp; register int i; register uint32_t hi; hi = key->key.high; for (cp = authdes_cache, i = 0; i < AUTHDES_CACHESZ; ++i, ++cp) { if (cp->key.key.high == hi && cp->key.key.low == key->key.low && cp->rname != NULL && memcmp (cp->rname, name, strlen (name) + 1) == 0) { if (BEFORE (timestamp, &cp->laststamp)) { ++svcauthdes_stats.ncachereplays; return -1; /* replay */ } ++svcauthdes_stats.ncachehits; return i; /* refresh */ } } ++svcauthdes_stats.ncachemisses; return cache_victim (); /* new credential */ } /* * Local credential handling stuff. * NOTE: bsd unix dependent. * Other operating systems should put something else here. */ #define UNKNOWN -2 /* grouplen, if cached cred is unknown user */ #define INVALID -1 /* grouplen, if cache entry is invalid */ struct bsdcred { uid_t uid; /* cached uid */ gid_t gid; /* cached gid */ int grouplen; /* length of cached groups */ int grouplen_max; /* length of allocated cached groups */ gid_t groups[0]; /* cached groups */ }; /* * Map a des credential into a unix cred. * We cache the credential here so the application does * not have to make an rpc call every time to interpret * the credential. */ int authdes_getucred (const struct authdes_cred *adc, uid_t * uid, gid_t * gid, short *grouplen, gid_t * groups) { unsigned sid; register int i; uid_t i_uid; gid_t i_gid; int i_grouplen; struct bsdcred *cred; sid = adc->adc_nickname; if (sid >= AUTHDES_CACHESZ) { debug ("invalid nickname"); return 0; } cred = (struct bsdcred *) authdes_cache[sid].localcred; if (cred == NULL || cred->grouplen == INVALID) { /* * not in cache: lookup */ if (!netname2user (adc->adc_fullname.name, &i_uid, &i_gid, &i_grouplen, groups)) { debug ("unknown netname"); if (cred != NULL) cred->grouplen = UNKNOWN; /* mark as lookup up, but not found */ return 0; } if (cred != NULL && cred->grouplen_max < i_grouplen) { /* We already have an allocated data structure. But it is too small. */ free (cred); authdes_cache[sid].localcred = NULL; cred = NULL; } if (cred == NULL) { /* We should allocate room for at least NGROUPS groups. */ int ngroups_max = MAX (i_grouplen, NGROUPS); cred = (struct bsdcred *) mem_alloc (sizeof (struct bsdcred) + ngroups_max * sizeof (gid_t)); if (cred == NULL) return 0; authdes_cache[sid].localcred = (char *) cred; cred->grouplen = INVALID; cred->grouplen_max = ngroups_max; } debug ("missed ucred cache"); *uid = cred->uid = i_uid; *gid = cred->gid = i_gid; cred->grouplen = i_grouplen; for (i = i_grouplen - 1; i >= 0; --i) cred->groups[i] = groups[i]; /* Make sure no too large values are reported. */ *grouplen = MIN (SHRT_MAX, i_grouplen); return 1; } else if (cred->grouplen == UNKNOWN) { /* * Already lookup up, but no match found */ return 0; } /* * cached credentials */ *uid = cred->uid; *gid = cred->gid; /* Another stupidity in the interface: *grouplen is of type short. So we might have to cut the information passed up short. */ int grouplen_copy = MIN (SHRT_MAX, cred->grouplen); *grouplen = grouplen_copy; for (i = grouplen_copy - 1; i >= 0; --i) groups[i] = cred->groups[i]; return 1; } libc_hidden_nolink_sunrpc (authdes_getucred, GLIBC_2_1) static void internal_function invalidate (char *cred) { if (cred == NULL) return; ((struct bsdcred *) cred)->grouplen = INVALID; }