1 /* $OpenBSD: skeyinit.c,v 1.45 2003/11/26 00:05:27 espie Exp $ */ 2 3 /* OpenBSD S/Key (skeyinit.c) 4 * 5 * Authors: 6 * Neil M. Haller <nmh@thumper.bellcore.com> 7 * Philip R. Karn <karn@chicago.qualcomm.com> 8 * John S. Walden <jsw@thumper.bellcore.com> 9 * Scott Chasin <chasin@crimelab.com> 10 * Todd C. Miller <Todd.Miller@courtesan.com> 11 * 12 * S/Key initialization and seed update 13 */ 14 15 #include <sys/param.h> 16 #include <sys/file.h> 17 #include <sys/resource.h> 18 #include <sys/stat.h> 19 #include <sys/time.h> 20 21 #include <ctype.h> 22 #include <err.h> 23 #include <errno.h> 24 #include <pwd.h> 25 #include <readpassphrase.h> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <syslog.h> 30 #include <time.h> 31 #include <unistd.h> 32 #include <utmp.h> 33 34 #include <skey.h> 35 #include <bsd_auth.h> 36 37 #ifndef SKEY_NAMELEN 38 #define SKEY_NAMELEN 4 39 #endif 40 41 void usage(void); 42 void secure_mode(int *, char *, char *, size_t, char *, size_t); 43 void normal_mode(char *, int, char *, char *); 44 void timedout(int); 45 void convert_db(void); 46 void enable_db(int); 47 48 int 49 main(int argc, char **argv) 50 { 51 int rval, i, l, n, defaultsetup, rmkey, hexmode, enable, convert; 52 char hostname[MAXHOSTNAMELEN]; 53 char seed[SKEY_MAX_SEED_LEN + 1]; 54 char buf[256], key[SKEY_BINKEY_SIZE], filename[PATH_MAX], *ht; 55 char lastc, me[UT_NAMESIZE + 1], *p, *auth_type; 56 u_int32_t noise; 57 struct skey skey; 58 struct passwd *pp; 59 60 n = rmkey = hexmode = enable = convert = 0; 61 defaultsetup = 1; 62 ht = auth_type = NULL; 63 64 /* Build up a default seed based on the hostname and some noise */ 65 if (gethostname(hostname, sizeof(hostname)) < 0) 66 err(1, "gethostname"); 67 for (i = 0, p = seed; hostname[i] && i < SKEY_NAMELEN; i++) { 68 if (isalpha(hostname[i])) { 69 if (isupper(hostname[i])) 70 hostname[i] = tolower(hostname[i]); 71 *p++ = hostname[i]; 72 } else if (isdigit(hostname[i])) 73 *p++ = hostname[i]; 74 } 75 noise = arc4random(); 76 for (i = 0; i < 5; i++) { 77 *p++ = (noise % 10) + '0'; 78 noise /= 10; 79 } 80 *p = '\0'; 81 82 if ((pp = getpwuid(getuid())) == NULL) 83 err(1, "no user with uid %u", getuid()); 84 (void)strlcpy(me, pp->pw_name, sizeof me); 85 86 if ((pp = getpwnam(me)) == NULL) 87 err(1, "Who are you?"); 88 89 for (i = 1; i < argc && argv[i][0] == '-' && strcmp(argv[i], "--");) { 90 if (argv[i][2] == '\0') { 91 /* Single character switch */ 92 switch (argv[i][1]) { 93 case 'a': 94 if (argv[++i] == NULL || argv[i][0] == '\0') 95 usage(); 96 if (auth_type == NULL) 97 auth_type = argv[i]; 98 break; 99 case 's': 100 defaultsetup = 0; 101 auth_type = "skey"; 102 break; 103 case 'x': 104 hexmode = 1; 105 break; 106 case 'r': 107 rmkey = 1; 108 break; 109 case 'n': 110 if (argv[++i] == NULL || argv[i][0] == '\0') 111 usage(); 112 if ((n = atoi(argv[i])) < 1 || n >= SKEY_MAX_SEQ) 113 errx(1, "count must be > 0 and < %d", 114 SKEY_MAX_SEQ); 115 break; 116 case 'C': 117 convert = 1; 118 break; 119 case 'D': 120 enable = -1; 121 break; 122 case 'E': 123 enable = 1; 124 break; 125 default: 126 usage(); 127 } 128 } else { 129 /* Multi character switches are hash types */ 130 if ((ht = skey_set_algorithm(&argv[i][1])) == NULL) { 131 warnx("Unknown hash algorithm %s", &argv[i][1]); 132 usage(); 133 } 134 } 135 i++; 136 } 137 argv += i; 138 argc -= i; 139 140 if (argc > 1 || (enable && convert) || (enable && argc) || 141 (convert && argc)) 142 usage(); 143 144 /* Handle -C, -D, and -E */ 145 if (convert || enable) { 146 if (convert) 147 convert_db(); 148 else 149 enable_db(enable); 150 exit(0); 151 } 152 153 /* Check for optional user string. */ 154 if (argc == 1) { 155 if ((pp = getpwnam(argv[0])) == NULL) { 156 if (getuid() == 0) { 157 static struct passwd _pp; 158 159 _pp.pw_name = argv[0]; 160 pp = &_pp; 161 warnx("Warning, user unknown: %s", argv[0]); 162 } else { 163 errx(1, "User unknown: %s", argv[0]); 164 } 165 } else if (strcmp(pp->pw_name, me) != 0 && getuid() != 0) { 166 /* Only root can change other's S/Keys. */ 167 errx(1, "Permission denied."); 168 } 169 } 170 171 switch (skey_haskey(pp->pw_name)) { 172 case -1: 173 if (errno == ENOENT || errno == EPERM) 174 errx(1, "S/Key disabled"); 175 else 176 err(1, "cannot open database"); 177 break; 178 case 0: 179 /* existing user */ 180 break; 181 case 1: 182 if (!defaultsetup) { 183 fprintf(stderr, 184 "You must authenticate yourself before using S/Key for the first time. In\n" 185 "secure mode this is normally done via an existing S/Key key. However, since\n" 186 "you do not have an entry in the S/Key database you will have to specify an\n" 187 "alternate authentication type via the `-a' flag, e.g.\n" 188 " \"skeyinit -s -a krb5\" or \"skeyinit -s -a passwd\"\n\n" 189 "Note that entering a plaintext password over a non-secure link defeats the\n" 190 "purpose of using S/Key in the fist place.\n"); 191 exit(1); 192 } 193 break; 194 } 195 196 if (defaultsetup) 197 fputs("Reminder - Only use this method if you are directly " 198 "connected\n or have an encrypted channel. If " 199 "you are using telnet,\n hit return now and use " 200 "skeyinit -s.\n", stderr); 201 202 if (getuid() != 0) { 203 if ((pp = pw_dup(pp)) == NULL) 204 err(1, NULL); 205 if (!auth_userokay(pp->pw_name, auth_type, NULL, NULL)) 206 errx(1, "Password incorrect"); 207 } 208 209 /* 210 * Lookup and lock the record we are about to modify. 211 * If this is a new entry this will prevent other users 212 * from appending new entries (and clobbering ours). 213 */ 214 rval = skeylookup(&skey, pp->pw_name); 215 switch (rval) { 216 case -1: 217 err(1, "cannot open database"); 218 break; 219 case 0: 220 /* remove user if asked to do so */ 221 if (rmkey) { 222 if (snprintf(filename, sizeof(filename), 223 "%s/%s", _PATH_SKEYDIR, pp->pw_name) 224 >= sizeof(filename)) { 225 errno = ENAMETOOLONG; 226 err(1, "Cannot remove S/Key entry"); 227 } 228 if (unlink(filename) != 0) 229 err(1, "Cannot remove S/Key entry"); 230 printf("S/Key entry for %s removed.\n", 231 pp->pw_name); 232 exit(0); 233 } 234 235 (void)printf("[Updating %s with %s]\n", pp->pw_name, 236 ht ? ht : skey_get_algorithm()); 237 (void)printf("Old seed: [%s] %s\n", 238 skey_get_algorithm(), skey.seed); 239 240 /* 241 * Sanity check old seed. 242 */ 243 l = strlen(skey.seed); 244 for (p = skey.seed; *p; p++) { 245 if (isalpha(*p)) { 246 if (isupper(*p)) 247 *p = tolower(*p); 248 } else if (!isdigit(*p)) { 249 memmove(p, p + 1, l - (p - skey.seed)); 250 l--; 251 } 252 } 253 254 /* If the seed ends in 0-8 just add one. */ 255 if (l > 0) { 256 lastc = skey.seed[l - 1]; 257 if (isdigit(lastc) && lastc != '9') { 258 (void)strlcpy(seed, skey.seed, 259 sizeof seed); 260 seed[l - 1] = lastc + 1; 261 } 262 if (isdigit(lastc) && lastc == '9' && l < 16) { 263 (void)strlcpy(seed, skey.seed, 264 sizeof seed); 265 seed[l - 1] = '0'; 266 seed[l] = '0'; 267 seed[l + 1] = '\0'; 268 } 269 } 270 break; 271 case 1: 272 if (rmkey) 273 errx(1, "You have no entry to remove."); 274 (void)printf("[Adding %s with %s]\n", pp->pw_name, 275 ht ? ht : skey_get_algorithm()); 276 if (snprintf(filename, sizeof(filename), "%s/%s", 277 _PATH_SKEYDIR, pp->pw_name) >= sizeof(filename)) { 278 errno = ENAMETOOLONG; 279 err(1, "Cannot create S/Key entry"); 280 } 281 if ((l = open(filename, 282 O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC |O_NOFOLLOW, 283 S_IRUSR | S_IWUSR)) == -1 || 284 flock(l, LOCK_EX) != 0 || 285 (skey.keyfile = fdopen(l, "r+")) == NULL) 286 err(1, "Cannot create S/Key entry"); 287 break; 288 } 289 if (fchown(fileno(skey.keyfile), pp->pw_uid, -1) != 0 || 290 fchmod(fileno(skey.keyfile), S_IRUSR | S_IWUSR) != 0) 291 err(1, "can't set owner/mode for %s", pp->pw_name); 292 if (n == 0) 293 n = 99; 294 295 /* Set hash type if asked to */ 296 if (ht && strcmp(ht, skey_get_algorithm()) != 0) 297 skey_set_algorithm(ht); 298 299 alarm(180); 300 if (!defaultsetup) 301 secure_mode(&n, key, seed, sizeof seed, buf, sizeof(buf)); 302 else 303 normal_mode(pp->pw_name, n, key, seed); 304 alarm(0); 305 306 /* XXX - why use malloc here? */ 307 if ((skey.val = (char *)malloc(16 + 1)) == NULL) 308 err(1, "Can't allocate memory"); 309 btoa8(skey.val, key); 310 311 (void)fseek(skey.keyfile, 0L, SEEK_SET); 312 (void)fprintf(skey.keyfile, "%s\n%s\n%04d\n%s\n%s\n", 313 pp->pw_name, skey_get_algorithm(), n, seed, skey.val); 314 (void)fclose(skey.keyfile); 315 316 (void)printf("\nID %s skey is otp-%s %d %s\n", pp->pw_name, 317 skey_get_algorithm(), n, seed); 318 (void)printf("Next login password: %s\n\n", 319 hexmode ? put8(buf, key) : btoe(buf, key)); 320 exit(0); 321 } 322 323 void 324 secure_mode(int *count, char *key, char *seed, size_t seedlen, 325 char *buf, size_t bufsiz) 326 { 327 char *p, newseed[SKEY_MAX_SEED_LEN + 2]; 328 int i, n; 329 330 (void)puts("You need the 6 words generated from the \"skey\" command."); 331 for (i = 0; ; i++) { 332 if (i >= 2) 333 exit(1); 334 335 (void)printf("Enter sequence count from 1 to %d: ", 336 SKEY_MAX_SEQ); 337 (void)fgets(buf, bufsiz, stdin); 338 clearerr(stdin); 339 n = atoi(buf); 340 if (n > 0 && n < SKEY_MAX_SEQ) 341 break; /* Valid range */ 342 (void)fprintf(stderr, "ERROR: Count must be between 1 and %d\n", 343 SKEY_MAX_SEQ); 344 } 345 346 for (i = 0; ; i++) { 347 if (i >= 2) 348 exit(1); 349 350 (void)printf("Enter new seed [default %s]: ", seed); 351 (void)fgets(newseed, sizeof(newseed), stdin); /* XXX */ 352 clearerr(stdin); 353 rip(newseed); 354 if (strlen(newseed) > SKEY_MAX_SEED_LEN) { 355 (void)fprintf(stderr, "ERROR: Seed must be between 1 " 356 "and %d characters in length\n", SKEY_MAX_SEED_LEN); 357 continue; 358 } 359 for (p = newseed; *p; p++) { 360 if (isspace(*p)) { 361 (void)fputs("ERROR: Seed must not contain " 362 "any spaces\n", stderr); 363 break; 364 } else if (isalpha(*p)) { 365 if (isupper(*p)) 366 *p = tolower(*p); 367 } else if (!isdigit(*p)) { 368 (void)fputs("ERROR: Seed must be purely " 369 "alphanumeric\n", stderr); 370 break; 371 } 372 } 373 if (*p == '\0') 374 break; /* Valid seed */ 375 } 376 if (newseed[0] != '\0') 377 (void)strlcpy(seed, newseed, seedlen); 378 379 for (i = 0; ; i++) { 380 if (i >= 2) 381 exit(1); 382 383 (void)printf("otp-%s %d %s\nS/Key access password: ", 384 skey_get_algorithm(), n, seed); 385 (void)fgets(buf, bufsiz, stdin); 386 clearerr(stdin); 387 rip(buf); 388 backspace(buf); 389 390 if (buf[0] == '?') { 391 (void)puts("Enter 6 words from secure S/Key calculation."); 392 continue; 393 } else if (buf[0] == '\0') 394 exit(1); 395 396 if (etob(key, buf) == 1 || atob8(key, buf) == 0) 397 break; /* Valid format */ 398 (void)fputs("ERROR: Invalid format - try again with the 6 words.\n", 399 stderr); 400 } 401 *count= n; 402 } 403 404 void 405 normal_mode(char *username, int n, char *key, char *seed) 406 { 407 int i, nn; 408 char passwd[SKEY_MAX_PW_LEN+2], key2[SKEY_BINKEY_SIZE]; 409 410 /* Get user's secret passphrase */ 411 for (i = 0; ; i++) { 412 if (i > 2) 413 errx(1, "S/Key entry not updated"); 414 415 if (readpassphrase("Enter secret passphrase: ", passwd, 416 sizeof(passwd), 0) == NULL || passwd[0] == '\0') 417 exit(1); 418 419 if (strlen(passwd) < SKEY_MIN_PW_LEN) { 420 (void)fprintf(stderr, 421 "ERROR: Your passphrase must be at least %d " 422 "characters long.\n", SKEY_MIN_PW_LEN); 423 continue; 424 } else if (strcmp(passwd, username) == 0) { 425 (void)fputs("ERROR: Your passphrase may not be the " 426 "same as your user name.\n", stderr); 427 continue; 428 } else if (strspn(passwd, "abcdefghijklmnopqrstuvwxyz") == 429 strlen(passwd)) { 430 (void)fputs("ERROR: Your passphrase must contain more " 431 "than just lower case letters.\nWhitespace, " 432 "numbers, and punctuation are suggested.\n", 433 stderr); 434 continue; 435 } else if (strlen(passwd) > 63) { 436 (void)fprintf(stderr, "WARNING: Your passphrase is " 437 "longer than the recommended maximum length of 63\n"); 438 } 439 /* XXX - should check for passphrase that is really too long */ 440 441 /* Crunch seed and passphrase into starting key */ 442 nn = keycrunch(key, seed, passwd); 443 memset(passwd, 0, sizeof(passwd)); 444 if (nn != 0) 445 err(2, "key crunch failed"); 446 447 if (readpassphrase("Again secret passphrase: ", passwd, 448 sizeof(passwd), 0) == NULL || passwd[0] == '\0') 449 exit(1); 450 451 /* Crunch seed and passphrase into starting key */ 452 nn = keycrunch(key2, seed, passwd); 453 memset(passwd, 0, sizeof(passwd)); 454 if (nn != 0) 455 err(2, "key crunch failed"); 456 457 if (memcmp(key, key2, sizeof(key2)) == 0) 458 break; 459 460 (void)fputs("Passphrases do not match.\n", stderr); 461 } 462 463 nn = n; 464 while (nn-- != 0) 465 f(key); 466 } 467 468 void 469 enable_db(int op) 470 { 471 if (op == 1) { 472 /* enable */ 473 if (mkdir(_PATH_SKEYDIR, 01730) != 0 && errno != EEXIST) 474 err(1, "can't mkdir %s", _PATH_SKEYDIR); 475 if (chown(_PATH_SKEYDIR, geteuid(), getegid()) != 0) 476 err(1, "can't chown %s", _PATH_SKEYDIR); 477 if (chmod(_PATH_SKEYDIR, 01730) != 0) 478 err(1, "can't chmod %s", _PATH_SKEYDIR); 479 } else { 480 /* disable */ 481 if (chmod(_PATH_SKEYDIR, 0) != 0 && errno != ENOENT) 482 err(1, "can't chmod %s", _PATH_SKEYDIR); 483 } 484 } 485 486 #define _PATH_SKEYKEYS "/etc/skeykeys" 487 void 488 convert_db(void) 489 { 490 struct passwd *pp; 491 uid_t uid; 492 FILE *keyfile; 493 FILE *newfile; 494 char buf[256], *logname, *hashtype, *seed, *val, *cp; 495 char filename[PATH_MAX]; 496 int fd, n; 497 498 if ((keyfile = fopen(_PATH_SKEYKEYS, "r")) == NULL) 499 err(1, "can't open %s", _PATH_SKEYKEYS); 500 if (flock(fileno(keyfile), LOCK_EX) != 0) 501 err(1, "can't lock %s", _PATH_SKEYKEYS); 502 enable_db(1); 503 504 /* 505 * Loop over each entry in _PATH_SKEYKEYS, creating a file 506 * in _PATH_SKEYDIR for each one. 507 */ 508 while (fgets(buf, sizeof(buf), keyfile) != NULL) { 509 if (buf[0] == '#') 510 continue; 511 if ((logname = strtok(buf, " \t")) == NULL) 512 continue; 513 if ((cp = strtok(NULL, " \t")) == NULL) 514 continue; 515 if (isalpha(*cp)) { 516 hashtype = cp; 517 if ((cp = strtok(NULL, " \t")) == NULL) 518 continue; 519 } else 520 hashtype = "md4"; 521 n = atoi(cp); 522 if ((seed = strtok(NULL, " \t")) == NULL) 523 continue; 524 if ((val = strtok(NULL, " \t")) == NULL) 525 continue; 526 527 if ((pp = getpwnam(logname)) != NULL) 528 uid = pp->pw_uid; 529 else 530 uid = 0; 531 532 /* Now write the new-style record. */ 533 if (snprintf(filename, sizeof(filename), "%s/%s", _PATH_SKEYDIR, 534 logname) >= sizeof(filename)) { 535 errno = ENAMETOOLONG; 536 warn("%s", logname); 537 continue; 538 } 539 fd = open(filename, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); 540 if (fd == -1 || flock(fd, LOCK_EX) != 0 || 541 (newfile = fdopen(fd, "r+")) == NULL) { 542 warn("%s", logname); 543 continue; 544 } 545 (void)fprintf(newfile, "%s\n%s\n%04d\n%s\n%s\n", logname, 546 hashtype, n, seed, val); 547 (void)fchown(fileno(newfile), uid, -1); 548 (void)fclose(newfile); 549 } 550 printf("%s has been populated. NOTE: %s has *not* been removed.\n" 551 "It should be removed once you have verified that the new keys " 552 "work.\n", _PATH_SKEYDIR, _PATH_SKEYKEYS); 553 } 554 555 #define TIMEOUT_MSG "Timed out waiting for input.\n" 556 void 557 timedout(int signo) 558 { 559 560 write(STDERR_FILENO, TIMEOUT_MSG, sizeof(TIMEOUT_MSG) - 1); 561 _exit(1); 562 } 563 564 void 565 usage(void) 566 { 567 extern char *__progname; 568 569 (void)fprintf(stderr, "usage: %s [-r] [-s] [-x] [-C] [-D] [-E] " 570 "[-a auth_type] [-n count]\n " 571 "[-md4|-md5|-sha1|-rmd160] [user]\n", __progname); 572 exit(1); 573 } 574