1 2 /* 3 * Copyright (c) 1988 by Sun Microsystems, Inc. 4 * 5 * @(#)svcauth_des.c 2.3 89/07/11 4.0 RPCSRC; from 1.15 88/02/08 SMI 6 * $FreeBSD: src/lib/libc/rpc/svc_auth_des.c,v 1.9 2002/03/22 23:18:37 obrien Exp $ 7 */ 8 9 /*- 10 * Copyright (c) 2009, Sun Microsystems, Inc. 11 * All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions are met: 15 * - Redistributions of source code must retain the above copyright notice, 16 * this list of conditions and the following disclaimer. 17 * - Redistributions in binary form must reproduce the above copyright notice, 18 * this list of conditions and the following disclaimer in the documentation 19 * and/or other materials provided with the distribution. 20 * - Neither the name of Sun Microsystems, Inc. nor the names of its 21 * contributors may be used to endorse or promote products derived 22 * from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 * POSSIBILITY OF SUCH DAMAGE. 35 */ 36 37 /* 38 * svcauth_des.c, server-side des authentication 39 * 40 * We insure for the service the following: 41 * (1) The timestamp microseconds do not exceed 1 million. 42 * (2) The timestamp plus the window is less than the current time. 43 * (3) The timestamp is not less than the one previously 44 * seen in the current session. 45 * 46 * It is up to the server to determine if the window size is 47 * too small . 48 * 49 */ 50 51 #include "namespace.h" 52 #include "reentrant.h" 53 #include <string.h> 54 #include <stdlib.h> 55 #include <stdio.h> 56 #include <unistd.h> 57 #include <rpc/des_crypt.h> 58 #include <sys/param.h> 59 #include <netinet/in.h> 60 #include <rpc/types.h> 61 #include <rpc/xdr.h> 62 #include <rpc/auth.h> 63 #include <rpc/auth_des.h> 64 #include <rpc/svc.h> 65 #include <rpc/rpc_msg.h> 66 #include <rpc/svc_auth.h> 67 #include "libc_private.h" 68 69 extern int key_decryptsession_pk(const char *, netobj *, des_block *); 70 71 #define debug(msg) printf("svcauth_des: %s\n", msg) 72 73 #define USEC_PER_SEC ((u_long) 1000000L) 74 #define BEFORE(t1, t2) timercmp(t1, t2, <) 75 76 /* 77 * LRU cache of conversation keys and some other useful items. 78 */ 79 #define AUTHDES_CACHESZ 64 80 struct cache_entry { 81 des_block key; /* conversation key */ 82 char *rname; /* client's name */ 83 u_int window; /* credential lifetime window */ 84 struct timeval laststamp; /* detect replays of creds */ 85 char *localcred; /* generic local credential */ 86 }; 87 static struct cache_entry *authdes_cache/* [AUTHDES_CACHESZ] */; 88 static short *authdes_lru/* [AUTHDES_CACHESZ] */; 89 90 static void cache_init(void); /* initialize the cache */ 91 static short cache_spot(des_block *, char *, struct timeval *); 92 /* find an entry in the cache */ 93 static void cache_ref(short sid); /* note that sid was ref'd */ 94 95 static void invalidate(char *); /* invalidate entry in cache */ 96 97 /* 98 * cache statistics 99 */ 100 static struct { 101 u_long ncachehits; /* times cache hit, and is not replay */ 102 u_long ncachereplays; /* times cache hit, and is replay */ 103 u_long ncachemisses; /* times cache missed */ 104 } svcauthdes_stats; 105 106 /* 107 * Service side authenticator for AUTH_DES 108 */ 109 enum auth_stat 110 _svcauth_des(struct svc_req *rqst, struct rpc_msg *msg) 111 { 112 113 long *ixdr; 114 des_block cryptbuf[2]; 115 struct authdes_cred *cred; 116 struct authdes_verf verf; 117 int status; 118 struct cache_entry *entry; 119 short sid = 0; 120 des_block *sessionkey; 121 des_block ivec; 122 u_int window; 123 struct timeval timestamp; 124 u_long namelen; 125 struct area { 126 struct authdes_cred area_cred; 127 char area_netname[MAXNETNAMELEN+1]; 128 } *area; 129 130 if (authdes_cache == NULL) { 131 cache_init(); 132 } 133 134 area = (struct area *)rqst->rq_clntcred; 135 cred = (struct authdes_cred *)&area->area_cred; 136 137 /* 138 * Get the credential 139 */ 140 ixdr = (long *)msg->rm_call.cb_cred.oa_base; 141 cred->adc_namekind = IXDR_GET_ENUM(ixdr, enum authdes_namekind); 142 switch (cred->adc_namekind) { 143 case ADN_FULLNAME: 144 namelen = IXDR_GET_U_LONG(ixdr); 145 if (namelen > MAXNETNAMELEN) { 146 return (AUTH_BADCRED); 147 } 148 cred->adc_fullname.name = area->area_netname; 149 bcopy((char *)ixdr, cred->adc_fullname.name, 150 (u_int)namelen); 151 cred->adc_fullname.name[namelen] = 0; 152 ixdr += (RNDUP(namelen) / BYTES_PER_XDR_UNIT); 153 cred->adc_fullname.key.key.high = (u_long)*ixdr++; 154 cred->adc_fullname.key.key.low = (u_long)*ixdr++; 155 cred->adc_fullname.window = (u_long)*ixdr++; 156 break; 157 case ADN_NICKNAME: 158 cred->adc_nickname = (u_long)*ixdr++; 159 break; 160 default: 161 return (AUTH_BADCRED); 162 } 163 164 /* 165 * Get the verifier 166 */ 167 ixdr = (long *)msg->rm_call.cb_verf.oa_base; 168 verf.adv_xtimestamp.key.high = (u_long)*ixdr++; 169 verf.adv_xtimestamp.key.low = (u_long)*ixdr++; 170 verf.adv_int_u = (u_long)*ixdr++; 171 172 173 /* 174 * Get the conversation key 175 */ 176 if (cred->adc_namekind == ADN_FULLNAME) { 177 netobj pkey; 178 char pkey_data[1024]; 179 180 sessionkey = &cred->adc_fullname.key; 181 if (! getpublickey(cred->adc_fullname.name, pkey_data)) { 182 debug("getpublickey"); 183 return(AUTH_BADCRED); 184 } 185 pkey.n_bytes = pkey_data; 186 pkey.n_len = strlen(pkey_data) + 1; 187 if (key_decryptsession_pk(cred->adc_fullname.name, &pkey, 188 sessionkey) < 0) { 189 debug("decryptsessionkey"); 190 return (AUTH_BADCRED); /* key not found */ 191 } 192 } else { /* ADN_NICKNAME */ 193 sid = (short)cred->adc_nickname; 194 if (sid < 0 || sid >= AUTHDES_CACHESZ) { 195 debug("bad nickname"); 196 return (AUTH_BADCRED); /* garbled credential */ 197 } 198 sessionkey = &authdes_cache[sid].key; 199 } 200 201 202 /* 203 * Decrypt the timestamp 204 */ 205 cryptbuf[0] = verf.adv_xtimestamp; 206 if (cred->adc_namekind == ADN_FULLNAME) { 207 cryptbuf[1].key.high = cred->adc_fullname.window; 208 cryptbuf[1].key.low = verf.adv_winverf; 209 ivec.key.high = ivec.key.low = 0; 210 status = cbc_crypt((char *)sessionkey, (char *)cryptbuf, 211 2*sizeof(des_block), DES_DECRYPT | DES_HW, 212 (char *)&ivec); 213 } else { 214 status = ecb_crypt((char *)sessionkey, (char *)cryptbuf, 215 sizeof(des_block), DES_DECRYPT | DES_HW); 216 } 217 if (DES_FAILED(status)) { 218 debug("decryption failure"); 219 return (AUTH_FAILED); /* system error */ 220 } 221 222 /* 223 * XDR the decrypted timestamp 224 */ 225 ixdr = (long *)cryptbuf; 226 timestamp.tv_sec = IXDR_GET_LONG(ixdr); 227 timestamp.tv_usec = IXDR_GET_LONG(ixdr); 228 229 /* 230 * Check for valid credentials and verifiers. 231 * They could be invalid because the key was flushed 232 * out of the cache, and so a new session should begin. 233 * Be sure and send AUTH_REJECTED{CRED, VERF} if this is the case. 234 */ 235 { 236 struct timeval current; 237 int nick; 238 int winverf; 239 240 if (cred->adc_namekind == ADN_FULLNAME) { 241 window = IXDR_GET_U_LONG(ixdr); 242 winverf = IXDR_GET_U_LONG(ixdr); 243 if (winverf != window - 1) { 244 debug("window verifier mismatch"); 245 return (AUTH_BADCRED); /* garbled credential */ 246 } 247 sid = cache_spot(sessionkey, cred->adc_fullname.name, 248 ×tamp); 249 if (sid < 0) { 250 debug("replayed credential"); 251 return (AUTH_REJECTEDCRED); /* replay */ 252 } 253 nick = 0; 254 } else { /* ADN_NICKNAME */ 255 window = authdes_cache[sid].window; 256 nick = 1; 257 } 258 259 if ((u_long)timestamp.tv_usec >= USEC_PER_SEC) { 260 debug("invalid usecs"); 261 /* cached out (bad key), or garbled verifier */ 262 return (nick ? AUTH_REJECTEDVERF : AUTH_BADVERF); 263 } 264 if (nick && BEFORE(×tamp, 265 &authdes_cache[sid].laststamp)) { 266 debug("timestamp before last seen"); 267 return (AUTH_REJECTEDVERF); /* replay */ 268 } 269 gettimeofday(¤t, NULL); 270 current.tv_sec -= window; /* allow for expiration */ 271 if (!BEFORE(¤t, ×tamp)) { 272 debug("timestamp expired"); 273 /* replay, or garbled credential */ 274 return (nick ? AUTH_REJECTEDVERF : AUTH_BADCRED); 275 } 276 } 277 278 /* 279 * Set up the reply verifier 280 */ 281 verf.adv_nickname = (u_long)sid; 282 283 /* 284 * xdr the timestamp before encrypting 285 */ 286 ixdr = (long *)cryptbuf; 287 IXDR_PUT_LONG(ixdr, timestamp.tv_sec - 1); 288 IXDR_PUT_LONG(ixdr, timestamp.tv_usec); 289 290 /* 291 * encrypt the timestamp 292 */ 293 status = ecb_crypt((char *)sessionkey, (char *)cryptbuf, 294 sizeof(des_block), DES_ENCRYPT | DES_HW); 295 if (DES_FAILED(status)) { 296 debug("encryption failure"); 297 return (AUTH_FAILED); /* system error */ 298 } 299 verf.adv_xtimestamp = cryptbuf[0]; 300 301 /* 302 * Serialize the reply verifier, and update rqst 303 */ 304 ixdr = (long *)msg->rm_call.cb_verf.oa_base; 305 *ixdr++ = (long)verf.adv_xtimestamp.key.high; 306 *ixdr++ = (long)verf.adv_xtimestamp.key.low; 307 *ixdr++ = (long)verf.adv_int_u; 308 309 rqst->rq_xprt->xp_verf.oa_flavor = AUTH_DES; 310 rqst->rq_xprt->xp_verf.oa_base = msg->rm_call.cb_verf.oa_base; 311 rqst->rq_xprt->xp_verf.oa_length = 312 (char *)ixdr - msg->rm_call.cb_verf.oa_base; 313 314 /* 315 * We succeeded, commit the data to the cache now and 316 * finish cooking the credential. 317 */ 318 entry = &authdes_cache[sid]; 319 entry->laststamp = timestamp; 320 cache_ref(sid); 321 if (cred->adc_namekind == ADN_FULLNAME) { 322 cred->adc_fullname.window = window; 323 cred->adc_nickname = (u_long)sid; /* save nickname */ 324 if (entry->rname != NULL) { 325 mem_free(entry->rname, strlen(entry->rname) + 1); 326 } 327 entry->rname = (char *)mem_alloc((u_int)strlen(cred->adc_fullname.name) 328 + 1); 329 if (entry->rname != NULL) { 330 strcpy(entry->rname, cred->adc_fullname.name); 331 } else { 332 debug("out of memory"); 333 } 334 entry->key = *sessionkey; 335 entry->window = window; 336 invalidate(entry->localcred); /* mark any cached cred invalid */ 337 } else { /* ADN_NICKNAME */ 338 /* 339 * nicknames are cooked into fullnames 340 */ 341 cred->adc_namekind = ADN_FULLNAME; 342 cred->adc_fullname.name = entry->rname; 343 cred->adc_fullname.key = entry->key; 344 cred->adc_fullname.window = entry->window; 345 } 346 return (AUTH_OK); /* we made it!*/ 347 } 348 349 350 /* 351 * Initialize the cache 352 */ 353 static void 354 cache_init(void) 355 { 356 int i; 357 358 authdes_cache = (struct cache_entry *) 359 mem_alloc(sizeof(struct cache_entry) * AUTHDES_CACHESZ); 360 bzero((char *)authdes_cache, 361 sizeof(struct cache_entry) * AUTHDES_CACHESZ); 362 363 authdes_lru = (short *)mem_alloc(sizeof(short) * AUTHDES_CACHESZ); 364 /* 365 * Initialize the lru list 366 */ 367 for (i = 0; i < AUTHDES_CACHESZ; i++) { 368 authdes_lru[i] = i; 369 } 370 } 371 372 373 /* 374 * Find the lru victim 375 */ 376 static short 377 cache_victim(void) 378 { 379 return (authdes_lru[AUTHDES_CACHESZ-1]); 380 } 381 382 /* 383 * Note that sid was referenced 384 */ 385 static void 386 cache_ref(short sid) 387 { 388 int i; 389 short curr; 390 short prev; 391 392 prev = authdes_lru[0]; 393 authdes_lru[0] = sid; 394 for (i = 1; prev != sid; i++) { 395 curr = authdes_lru[i]; 396 authdes_lru[i] = prev; 397 prev = curr; 398 } 399 } 400 401 402 /* 403 * Find a spot in the cache for a credential containing 404 * the items given. Return -1 if a replay is detected, otherwise 405 * return the spot in the cache. 406 */ 407 static short 408 cache_spot(des_block *key, char *name, struct timeval *timestamp) 409 { 410 struct cache_entry *cp; 411 int i; 412 u_long hi; 413 414 hi = key->key.high; 415 for (cp = authdes_cache, i = 0; i < AUTHDES_CACHESZ; i++, cp++) { 416 if (cp->key.key.high == hi && 417 cp->key.key.low == key->key.low && 418 cp->rname != NULL && 419 bcmp(cp->rname, name, strlen(name) + 1) == 0) { 420 if (BEFORE(timestamp, &cp->laststamp)) { 421 svcauthdes_stats.ncachereplays++; 422 return (-1); /* replay */ 423 } 424 svcauthdes_stats.ncachehits++; 425 return (i); /* refresh */ 426 } 427 } 428 svcauthdes_stats.ncachemisses++; 429 return (cache_victim()); /* new credential */ 430 } 431 432 433 #if defined(sun) || defined(vax) || \ 434 defined(__FreeBSD__) || defined(__DragonFly__) 435 /* 436 * Local credential handling stuff. 437 * NOTE: bsd unix dependent. 438 * Other operating systems should put something else here. 439 */ 440 #define UNKNOWN -2 /* grouplen, if cached cred is unknown user */ 441 #define INVALID -1 /* grouplen, if cache entry is invalid */ 442 443 struct bsdcred { 444 short uid; /* cached uid */ 445 short gid; /* cached gid */ 446 short grouplen; /* length of cached groups */ 447 short groups[NGROUPS]; /* cached groups */ 448 }; 449 450 /* 451 * Map a des credential into a unix cred. 452 * We cache the credential here so the application does 453 * not have to make an rpc call every time to interpret 454 * the credential. 455 */ 456 int 457 authdes_getucred(struct authdes_cred *adc, uid_t *uid, gid_t *gid, 458 int *grouplen, gid_t *groups) 459 { 460 unsigned sid; 461 int i; 462 uid_t i_uid; 463 gid_t i_gid; 464 int i_grouplen; 465 struct bsdcred *cred; 466 467 sid = adc->adc_nickname; 468 if (sid >= AUTHDES_CACHESZ) { 469 debug("invalid nickname"); 470 return (0); 471 } 472 cred = (struct bsdcred *)authdes_cache[sid].localcred; 473 if (cred == NULL) { 474 cred = (struct bsdcred *)mem_alloc(sizeof(struct bsdcred)); 475 authdes_cache[sid].localcred = (char *)cred; 476 cred->grouplen = INVALID; 477 } 478 if (cred->grouplen == INVALID) { 479 /* 480 * not in cache: lookup 481 */ 482 if (!netname2user(adc->adc_fullname.name, &i_uid, &i_gid, 483 &i_grouplen, groups)) 484 { 485 debug("unknown netname"); 486 cred->grouplen = UNKNOWN; /* mark as lookup up, but not found */ 487 return (0); 488 } 489 debug("missed ucred cache"); 490 *uid = cred->uid = i_uid; 491 *gid = cred->gid = i_gid; 492 *grouplen = cred->grouplen = i_grouplen; 493 for (i = i_grouplen - 1; i >= 0; i--) { 494 cred->groups[i] = groups[i]; /* int to short */ 495 } 496 return (1); 497 } else if (cred->grouplen == UNKNOWN) { 498 /* 499 * Already lookup up, but no match found 500 */ 501 return (0); 502 } 503 504 /* 505 * cached credentials 506 */ 507 *uid = cred->uid; 508 *gid = cred->gid; 509 *grouplen = cred->grouplen; 510 for (i = cred->grouplen - 1; i >= 0; i--) { 511 groups[i] = cred->groups[i]; /* short to int */ 512 } 513 return (1); 514 } 515 516 static void 517 invalidate(char *cred) 518 { 519 if (cred == NULL) { 520 return; 521 } 522 ((struct bsdcred *)cred)->grouplen = INVALID; 523 } 524 #endif 525 526