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