skeylogin.c revision 1.25
1/* S/KEY v1.1b (skeylogin.c) 2 * 3 * Authors: 4 * Neil M. Haller <nmh@thumper.bellcore.com> 5 * Philip R. Karn <karn@chicago.qualcomm.com> 6 * John S. Walden <jsw@thumper.bellcore.com> 7 * Scott Chasin <chasin@crimelab.com> 8 * 9 * Modifications: 10 * Todd C. Miller <Todd.Miller@courtesan.com> 11 * Angelos D. Keromytis <adk@adk.gr> 12 * 13 * S/KEY verification check, lookups, and authentication. 14 * 15 * $OpenBSD: skeylogin.c,v 1.25 1998/07/03 01:32:49 angelos Exp $ 16 */ 17 18#include <sys/param.h> 19#include <sys/file.h> 20#ifdef QUOTA 21#include <sys/quota.h> 22#endif 23#include <sys/stat.h> 24#include <sys/time.h> 25#include <sys/resource.h> 26#include <sys/types.h> 27#include <sys/stat.h> 28 29#include <ctype.h> 30#include <err.h> 31#include <errno.h> 32#include <paths.h> 33#include <stdio.h> 34#include <stdlib.h> 35#include <string.h> 36#include <time.h> 37#include <unistd.h> 38#include <sha1.h> 39 40#include "skey.h" 41 42char *skipspace __P((char *)); 43int skeylookup __P((struct skey *, char *)); 44 45/* Issue a skey challenge for user 'name'. If successful, 46 * fill in the caller's skey structure and return(0). If unsuccessful 47 * (e.g., if name is unknown) return(-1). 48 * 49 * The file read/write pointer is left at the start of the 50 * record. 51 */ 52int 53getskeyprompt(mp, name, prompt) 54 struct skey *mp; 55 char *name; 56 char *prompt; 57{ 58 int rval; 59 60 sevenbit(name); 61 rval = skeylookup(mp, name); 62 (void)strcpy(prompt, "otp-md0 55 latour1\n"); 63 switch (rval) { 64 case -1: /* File error */ 65 return(-1); 66 case 0: /* Lookup succeeded, return challenge */ 67 (void)sprintf(prompt, "otp-%.*s %d %.*s\n", 68 SKEY_MAX_HASHNAME_LEN, skey_get_algorithm(), 69 mp->n - 1, SKEY_MAX_SEED_LEN, mp->seed); 70 return(0); 71 case 1: /* User not found */ 72 (void)fclose(mp->keyfile); 73 return(-1); 74 } 75 return(-1); /* Can't happen */ 76} 77 78/* Return a skey challenge string for user 'name'. If successful, 79 * fill in the caller's skey structure and return(0). If unsuccessful 80 * (e.g., if name is unknown) return(-1). 81 * 82 * The file read/write pointer is left at the start of the 83 * record. 84 */ 85int 86skeychallenge(mp, name, ss) 87 struct skey *mp; 88 char *name; 89 char *ss; 90{ 91 int rval; 92 93 rval = skeylookup(mp,name); 94 switch(rval){ 95 case -1: /* File error */ 96 return(-1); 97 case 0: /* Lookup succeeded, issue challenge */ 98 (void)sprintf(ss, "otp-%.*s %d %.*s", SKEY_MAX_HASHNAME_LEN, 99 skey_get_algorithm(), mp->n - 1, 100 SKEY_MAX_SEED_LEN, mp->seed); 101 return(0); 102 case 1: /* User not found */ 103 (void)fclose(mp->keyfile); 104 return(-1); 105 } 106 return(-1); /* Can't happen */ 107} 108 109/* Find an entry in the One-time Password database. 110 * Return codes: 111 * -1: error in opening database 112 * 0: entry found, file R/W pointer positioned at beginning of record 113 * 1: entry not found, file R/W pointer positioned at EOF 114 */ 115int 116skeylookup(mp, name) 117 struct skey *mp; 118 char *name; 119{ 120 int found = 0; 121 long recstart = 0; 122 char *cp, *ht = NULL; 123 struct stat statbuf; 124 125 /* Open _PATH_SKEYKEYS if it exists, else return an error */ 126 if (stat(_PATH_SKEYKEYS, &statbuf) == 0 && 127 (mp->keyfile = fopen(_PATH_SKEYKEYS, "r+")) != NULL) { 128 if ((statbuf.st_mode & 0007777) != 0600) 129 fchmod(fileno(mp->keyfile), 0600); 130 } else { 131 return(-1); 132 } 133 134 /* Look up user name in database */ 135 while (!feof(mp->keyfile)) { 136 recstart = ftell(mp->keyfile); 137 mp->recstart = recstart; 138 if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) != mp->buf) 139 break; 140 rip(mp->buf); 141 if (mp->buf[0] == '#') 142 continue; /* Comment */ 143 if ((mp->logname = strtok(mp->buf, " \t")) == NULL) 144 continue; 145 if ((cp = strtok(NULL, " \t")) == NULL) 146 continue; 147 /* Save hash type if specified, else use md4 */ 148 if (isalpha(*cp)) { 149 ht = cp; 150 if ((cp = strtok(NULL, " \t")) == NULL) 151 continue; 152 } else { 153 ht = "md4"; 154 } 155 mp->n = atoi(cp); 156 if ((mp->seed = strtok(NULL, " \t")) == NULL) 157 continue; 158 if ((mp->val = strtok(NULL, " \t")) == NULL) 159 continue; 160 if (strcmp(mp->logname, name) == 0) { 161 found = 1; 162 break; 163 } 164 } 165 if (found) { 166 (void)fseek(mp->keyfile, recstart, SEEK_SET); 167 /* Set hash type */ 168 if (ht && skey_set_algorithm(ht) == NULL) { 169 warnx("Unknown hash algorithm %s, using %s", ht, 170 skey_get_algorithm()); 171 } 172 return(0); 173 } else { 174 return(1); 175 } 176} 177 178/* Get the next entry in the One-time Password database. 179 * Return codes: 180 * -1: error in opening database 181 * 0: next entry found and stored in mp 182 * 1: no more entries, file R/W pointer positioned at EOF 183 */ 184int 185skeygetnext(mp) 186 struct skey *mp; 187{ 188 long recstart = 0; 189 char *cp; 190 struct stat statbuf; 191 192 /* Open _PATH_SKEYKEYS if it exists, else return an error */ 193 if (mp->keyfile == NULL) { 194 if (stat(_PATH_SKEYKEYS, &statbuf) == 0 && 195 (mp->keyfile = fopen(_PATH_SKEYKEYS, "r+")) != NULL) { 196 if ((statbuf.st_mode & 0007777) != 0600) 197 fchmod(fileno(mp->keyfile), 0600); 198 } else { 199 return(-1); 200 } 201 } 202 203 /* Look up next user in database */ 204 while (!feof(mp->keyfile)) { 205 recstart = ftell(mp->keyfile); 206 mp->recstart = recstart; 207 if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) != mp->buf) 208 break; 209 rip(mp->buf); 210 if (mp->buf[0] == '#') 211 continue; /* Comment */ 212 if ((mp->logname = strtok(mp->buf, " \t")) == NULL) 213 continue; 214 if ((cp = strtok(NULL, " \t")) == NULL) 215 continue; 216 /* Save hash type if specified, else use md4 */ 217 if (isalpha(*cp)) { 218 if ((cp = strtok(NULL, " \t")) == NULL) 219 continue; 220 } 221 mp->n = atoi(cp); 222 if ((mp->seed = strtok(NULL, " \t")) == NULL) 223 continue; 224 if ((mp->val = strtok(NULL, " \t")) == NULL) 225 continue; 226 /* Got a real entry */ 227 break; 228 } 229 return(feof(mp->keyfile)); 230} 231 232/* Verify response to a s/key challenge. 233 * 234 * Return codes: 235 * -1: Error of some sort; database unchanged 236 * 0: Verify successful, database updated 237 * 1: Verify failed, database unchanged 238 * 239 * The database file is always closed by this call. 240 */ 241int 242skeyverify(mp, response) 243 struct skey *mp; 244 char *response; 245{ 246 char key[SKEY_BINKEY_SIZE]; 247 char fkey[SKEY_BINKEY_SIZE]; 248 char filekey[SKEY_BINKEY_SIZE]; 249 time_t now; 250 struct tm *tm; 251 char tbuf[27]; 252 char *cp; 253 int i, rval; 254 255 time(&now); 256 tm = localtime(&now); 257 (void)strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm); 258 259 if (response == NULL) { 260 (void)fclose(mp->keyfile); 261 return(-1); 262 } 263 rip(response); 264 265 /* Convert response to binary */ 266 if (etob(key, response) != 1 && atob8(key, response) != 0) { 267 /* Neither english words or ascii hex */ 268 (void)fclose(mp->keyfile); 269 return(-1); 270 } 271 272 /* Compute fkey = f(key) */ 273 (void)memcpy(fkey, key, sizeof(key)); 274 (void)fflush(stdout); 275 f(fkey); 276 277 /* 278 * Obtain an exclusive lock on the key file so the same password 279 * cannot be used twice to get in to the system. 280 */ 281 for (i = 0; i < 300; i++) { 282 if ((rval = flock(fileno(mp->keyfile), LOCK_EX|LOCK_NB)) == 0 || 283 errno != EWOULDBLOCK) 284 break; 285 usleep(100000); /* Sleep for 0.1 seconds */ 286 } 287 if (rval == -1) { /* Can't get exclusive lock */ 288 errno = EAGAIN; 289 return(-1); 290 } 291 292 /* Reread the file record NOW */ 293 (void)fseek(mp->keyfile, mp->recstart, SEEK_SET); 294 if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) != mp->buf) { 295 (void)fclose(mp->keyfile); 296 return(-1); 297 } 298 rip(mp->buf); 299 mp->logname = strtok(mp->buf, " \t"); 300 cp = strtok(NULL, " \t") ; 301 if (isalpha(*cp)) 302 cp = strtok(NULL, " \t") ; 303 mp->seed = strtok(NULL, " \t"); 304 mp->val = strtok(NULL, " \t"); 305 /* And convert file value to hex for comparison */ 306 atob8(filekey, mp->val); 307 308 /* Do actual comparison */ 309 if (memcmp(filekey, fkey, SKEY_BINKEY_SIZE) != 0){ 310 /* Wrong response */ 311 (void)fclose(mp->keyfile); 312 return(1); 313 } 314 315 /* 316 * Update key in database by overwriting entire record. Note 317 * that we must write exactly the same number of bytes as in 318 * the original record (note fixed width field for N) 319 */ 320 btoa8(mp->val,key); 321 mp->n--; 322 (void)fseek(mp->keyfile, mp->recstart, SEEK_SET); 323 /* Don't save algorithm type for md4 (keep record length same) */ 324 if (strcmp(skey_get_algorithm(), "md4") == 0) 325 (void)fprintf(mp->keyfile, "%s %04d %-16s %s %-21s\n", 326 mp->logname, mp->n, mp->seed, mp->val, tbuf); 327 else 328 (void)fprintf(mp->keyfile, "%s %s %04d %-16s %s %-21s\n", 329 mp->logname, skey_get_algorithm(), mp->n, 330 mp->seed, mp->val, tbuf); 331 332 (void)fclose(mp->keyfile); 333 334 return(0); 335} 336 337/* 338 * skey_haskey() 339 * 340 * Returns: 1 user doesnt exist, -1 fle error, 0 user exists. 341 * 342 */ 343int 344skey_haskey(username) 345 char *username; 346{ 347 struct skey skey; 348 349 return(skeylookup(&skey, username)); 350} 351 352/* 353 * skey_keyinfo() 354 * 355 * Returns the current sequence number and 356 * seed for the passed user. 357 * 358 */ 359char * 360skey_keyinfo(username) 361 char *username; 362{ 363 int i; 364 static char str[SKEY_MAX_CHALLENGE]; 365 struct skey skey; 366 367 i = skeychallenge(&skey, username, str); 368 if (i == -1) 369 return(0); 370 371 return(str); 372} 373 374/* 375 * skey_passcheck() 376 * 377 * Check to see if answer is the correct one to the current 378 * challenge. 379 * 380 * Returns: 0 success, -1 failure 381 * 382 */ 383int 384skey_passcheck(username, passwd) 385 char *username, *passwd; 386{ 387 int i; 388 struct skey skey; 389 390 i = skeylookup(&skey, username); 391 if (i == -1 || i == 1) 392 return(-1); 393 394 if (skeyverify(&skey, passwd) == 0) 395 return(skey.n); 396 397 return(-1); 398} 399 400#define ROUND(x) (((x)[0] << 24) + (((x)[1]) << 16) + (((x)[2]) << 8) + \ 401 ((x)[3])) 402 403/* 404 * hash_collapse() 405 */ 406static u_int32_t 407hash_collapse(s) 408 u_char *s; 409{ 410 int len, target; 411 u_int32_t i; 412 413 if ((strlen(s) % sizeof(u_int32_t)) == 0) 414 target = strlen(s); /* Multiple of 4 */ 415 else 416 target = strlen(s) - (strlen(s) % sizeof(u_int32_t)); 417 418 for (i = 0, len = 0; len < target; len += 4) 419 i ^= ROUND(s + len); 420 421 return i; 422} 423 424/* 425 * skey_authenticate() 426 * 427 * Used when calling program will allow input of the user's 428 * response to the challenge. 429 * 430 * Returns: 0 success, -1 failure 431 * 432 */ 433int 434skey_authenticate(username) 435 char *username; 436{ 437 int i, fd, ptr; 438 u_char hseed[SKEY_MAX_SEED_LEN], flg = 1, *up; 439 char pbuf[SKEY_MAX_PW_LEN+1], skeyprompt[SKEY_MAX_CHALLENGE+1]; 440 struct skey skey; 441 struct stat sb; 442 SHA1_CTX ctx; 443 444 /* Attempt an S/Key challenge */ 445 i = skeychallenge(&skey, username, skeyprompt); 446 447 /* Cons up a fake prompt if no entry in keys file */ 448 if (i != 0) { 449 char *p, *u; 450 451 /* 452 * Base first 4 chars of seed on hostname. 453 * Add some filler for short hostnames if necessary. 454 */ 455 if (gethostname(pbuf, sizeof(pbuf)) == -1) 456 *(p = pbuf) = '.'; 457 else 458 for (p = pbuf; *p && isalnum(*p); p++) 459 if (isalpha(*p) && isupper(*p)) 460 *p = tolower(*p); 461 if (*p && pbuf - p < 4) 462 (void)strncpy(p, "asjd", 4 - (pbuf - p)); 463 p = &pbuf[4]; 464 *p = '\0'; 465 466 /* See if the random file's there */ 467 if ((fd = open(_SKEY_RAND_FILE_PATH_, O_RDONLY)) != -1) { 468 if ((fstat(fd, &sb) != -1) && 469 ((up = SHA1Data(username, strlen(username), NULL)) 470 != NULL)) { 471 /* Collapse the hash */ 472 ptr = hash_collapse(up); 473 474 if ((lseek(fd, ptr % (sb.st_size - SKEY_MAX_SEED_LEN), SEEK_SET) != -1) && (read(fd, hseed, SKEY_MAX_SEED_LEN) == SKEY_MAX_SEED_LEN)) { 475 memset(up, 0, strlen(up)); 476 477 /* Hash secret value with username */ 478 SHA1Init(&ctx); 479 SHA1Update(&ctx, hseed, 480 SKEY_MAX_SEED_LEN); 481 SHA1Update(&ctx, username, 482 strlen(username)); 483 SHA1Final(up, &ctx); 484 485 /* Zero out */ 486 memset(hseed, 0, SKEY_MAX_SEED_LEN); 487 488 /* Now hash the hash */ 489 SHA1Init(&ctx); 490 SHA1Update(&ctx, up, strlen(up)); 491 SHA1Final(up, &ctx); 492 493 ptr = hash_collapse(up + 4); 494 495 for (i = 0; 496 i < SKEY_MAX_SEED_LEN; 497 i++) { 498 p[i] = (ptr % 10) + '0'; 499 ptr /= 10; 500 } 501 502 /* Sequence number */ 503 ptr = ((up[2] + up[3]) % 99) + 1; 504 505 memset(up, 0, 20); /* SHA1 specific */ 506 free(up); 507 flg = 0; 508 509 (void)sprintf(skeyprompt, 510 "otp-%.*s %d %.*s", 511 SKEY_MAX_HASHNAME_LEN, 512 skey_get_algorithm(), 513 ptr, SKEY_MAX_SEED_LEN, 514 pbuf); 515 /* Done */ 516 } else 517 free(up); 518 } 519 520 close(fd); 521 } 522 523 if (flg) 524 { 525 /* Base last 8 chars of seed on username */ 526 u = username; 527 i = 8; 528 do { 529 if (*u == 0) { 530 /* Pad remainder with zeros */ 531 while (--i >= 0) 532 *p++ = '0'; 533 break; 534 } 535 536 *p++ = (*u++ % 10) + '0'; 537 } while (--i != 0); 538 pbuf[12] = '\0'; 539 540 (void)sprintf(skeyprompt, "otp-%.*s %d %.*s", 541 SKEY_MAX_HASHNAME_LEN, skey_get_algorithm(), 542 99, SKEY_MAX_SEED_LEN, pbuf); 543 } 544 } 545 546 (void)fprintf(stderr, "%s\n", skeyprompt); 547 (void)fflush(stderr); 548 549 (void)fputs("Response: ", stderr); 550 readskey(pbuf, sizeof(pbuf)); 551 552 /* Is it a valid response? */ 553 if (i == 0 && skeyverify(&skey, pbuf) == 0) { 554 if (skey.n < 5) { 555 (void)fprintf(stderr, 556 "\nWarning! Key initialization needed soon. (%d logins left)\n", 557 skey.n); 558 } 559 return(0); 560 } 561 return(-1); 562} 563 564/* Comment out user's entry in the s/key database 565 * 566 * Return codes: 567 * -1: Write error; database unchanged 568 * 0: Database updated 569 * 570 * The database file is always closed by this call. 571 */ 572int 573skeyzero(mp, response) 574 struct skey *mp; 575 char *response; 576{ 577 /* 578 * Seek to the right place and write comment character 579 * which effectively zero's out the entry. 580 */ 581 (void)fseek(mp->keyfile, mp->recstart, SEEK_SET); 582 if (fputc('#', mp->keyfile) == EOF) { 583 fclose(mp->keyfile); 584 return(-1); 585 } 586 587 (void)fclose(mp->keyfile); 588 589 return(0); 590} 591