1139823Simp/* $OpenBSD: skeyinit.c,v 1.75 2019/06/28 13:35:03 deraadt Exp $ */ 21541Srgrimes 31541Srgrimes/* OpenBSD S/Key (skeyinit.c) 41541Srgrimes * 51541Srgrimes * Authors: 61541Srgrimes * Neil M. Haller <nmh@thumper.bellcore.com> 71541Srgrimes * Philip R. Karn <karn@chicago.qualcomm.com> 81541Srgrimes * John S. Walden <jsw@thumper.bellcore.com> 91541Srgrimes * Scott Chasin <chasin@crimelab.com> 101541Srgrimes * Todd C. Miller <millert@openbsd.org> 111541Srgrimes * 121541Srgrimes * S/Key initialization and seed update 131541Srgrimes */ 141541Srgrimes 151541Srgrimes#include <sys/stat.h> 161541Srgrimes 171541Srgrimes#include <ctype.h> 181541Srgrimes#include <err.h> 191541Srgrimes#include <errno.h> 201541Srgrimes#include <fcntl.h> 211541Srgrimes#include <pwd.h> 221541Srgrimes#include <readpassphrase.h> 231541Srgrimes#include <stdio.h> 241541Srgrimes#include <stdlib.h> 251541Srgrimes#include <string.h> 261541Srgrimes#include <syslog.h> 271541Srgrimes#include <time.h> 281541Srgrimes#include <unistd.h> 2985052Sru#include <limits.h> 3050477Speter#include <utmp.h> 311541Srgrimes 321541Srgrimes#include <skey.h> 332168Spaul#include <bsd_auth.h> 342168Spaul 352168Spaul#ifndef SKEY_NAMELEN 361541Srgrimes#define SKEY_NAMELEN 4 371541Srgrimes#endif 388876Srgrimes 391541Srgrimesvoid usage(void); 401541Srgrimesvoid secure_mode(int *, char *, char *, size_t, char *, size_t); 411541Srgrimesvoid normal_mode(char *, int, char *, char *); 421541Srgrimesvoid enable_db(int); 431541Srgrimes 441541Srgrimesint 451541Srgrimesmain(int argc, char **argv) 461541Srgrimes{ 471541Srgrimes int rval, i, l, n, defaultsetup, rmkey, hexmode, enable; 481541Srgrimes char hostname[HOST_NAME_MAX+1]; 491541Srgrimes char seed[SKEY_MAX_SEED_LEN + 1]; 501541Srgrimes char buf[256], key[SKEY_BINKEY_SIZE], filename[PATH_MAX], *ht; 511541Srgrimes char lastc, *p, *auth_type; 521541Srgrimes const char *errstr; 531541Srgrimes struct skey skey; 541541Srgrimes struct passwd *pp; 551541Srgrimes 561541Srgrimes n = rmkey = hexmode = enable = 0; 57122922Sandre defaultsetup = 1; 58122922Sandre ht = auth_type = NULL; 59122922Sandre 60122922Sandre for (i = 1; i < argc && argv[i][0] == '-' && strcmp(argv[i], "--");) { 61122922Sandre if (argv[i][2] == '\0') { 62122922Sandre /* Single character switch */ 631541Srgrimes switch (argv[i][1]) { 641541Srgrimes case 'a': 651541Srgrimes if (argv[++i] == NULL || argv[i][0] == '\0') 661541Srgrimes usage(); 671541Srgrimes auth_type = argv[i]; 6813765Smpp break; 6913765Smpp case 's': 701541Srgrimes defaultsetup = 0; 711541Srgrimes if (auth_type == NULL) 721541Srgrimes auth_type = "skey"; 731541Srgrimes break; 745791Swollman case 'x': 751541Srgrimes hexmode = 1; 761541Srgrimes break; 771541Srgrimes case 'r': 781541Srgrimes rmkey = 1; 791541Srgrimes break; 801541Srgrimes case 'n': 811541Srgrimes if (argv[++i] == NULL || argv[i][0] == '\0') 821541Srgrimes usage(); 831541Srgrimes n = strtonum(argv[i], 1, SKEY_MAX_SEQ - 1, &errstr); 841541Srgrimes if (errstr) 85183200Szec errx(1, "count must be > 0 and < %d", 86183200Szec SKEY_MAX_SEQ); 87183200Szec break; 88183200Szec case 'D': 89183200Szec enable = -1; 90183200Szec break; 91183200Szec case 'E': 92183200Szec enable = 1; 93183200Szec break; 94183200Szec default: 95183200Szec usage(); 96183200Szec } 97183200Szec } else { 98183200Szec /* Multi character switches are hash types */ 99183200Szec if ((ht = skey_set_algorithm(&argv[i][1])) == NULL) { 100183200Szec warnx("Unknown hash algorithm %s", &argv[i][1]); 101183200Szec usage(); 102183200Szec } 103183200Szec } 104183200Szec i++; 105183200Szec } 106178888Sjulian argv += i; 107178888Sjulian argc -= i; 108178888Sjulian 1091541Srgrimes if (argc > 1 || (enable && argc)) 1105833Sbde usage(); 1115833Sbde 1125833Sbde /* Handle -D and -E */ 1135833Sbde if (enable) { 1145833Sbde enable_db(enable); 1151541Srgrimes exit(0); 1161541Srgrimes } 1171541Srgrimes 1181541Srgrimes if (getuid() != 0) { 1191541Srgrimes if (pledge("stdio rpath wpath cpath fattr flock tty proc exec " 1201541Srgrimes "getpw", NULL) == -1) 1211541Srgrimes err(1, "pledge"); 1221541Srgrimes 1231541Srgrimes if ((pp = getpwuid(getuid())) == NULL) 124178167Sqingli err(1, "no user with uid %u", getuid()); 125178167Sqingli 1261541Srgrimes if (argc == 1) { 127178167Sqingli char me[UT_NAMESIZE + 1]; 1281541Srgrimes 1291541Srgrimes (void)strlcpy(me, pp->pw_name, sizeof me); 130128454Sluigi if ((pp = getpwnam(argv[0])) == NULL) 131128454Sluigi errx(1, "User unknown: %s", argv[0]); 132128454Sluigi if (strcmp(pp->pw_name, me) != 0) 133128454Sluigi errx(1, "Permission denied."); 134128454Sluigi } 135132780Skan } else { 136132780Skan if (pledge("stdio rpath wpath cpath fattr flock tty getpw id", 1371541Srgrimes NULL) == -1) 138186119Sqingli err(1, "pledge"); 139186119Sqingli 1401541Srgrimes if (argc == 1) { 141122922Sandre if ((pp = getpwnam(argv[0])) == NULL) { 142127828Sluigi static struct passwd _pp; 143178888Sjulian 144120727Ssam _pp.pw_name = argv[0]; 145120727Ssam pp = &_pp; 146120727Ssam warnx("Warning, user unknown: %s", argv[0]); 147120727Ssam } else { 1481541Srgrimes /* So the file ends up owned by the proper ID */ 1491541Srgrimes if (setresuid(-1, pp->pw_uid, -1) != 0) 1501541Srgrimes errx(1, "unable to change uid to %u", 1511541Srgrimes pp->pw_uid); 1521541Srgrimes } 1531541Srgrimes } else if ((pp = getpwuid(0)) == NULL) 1541541Srgrimes err(1, "no user with uid 0"); 1551541Srgrimes 1561541Srgrimes if (pledge("stdio rpath wpath cpath fattr flock tty", NULL) 1571541Srgrimes == -1) 1581541Srgrimes err(1, "pledge"); 1591541Srgrimes } 1601541Srgrimes 1611541Srgrimes switch (skey_haskey(pp->pw_name)) { 1621541Srgrimes case -1: 1631541Srgrimes if (errno == ENOENT || errno == EPERM) 1644104Swollman errx(1, "S/Key disabled"); 1654104Swollman else 1661541Srgrimes err(1, "cannot open database"); 1671541Srgrimes break; 1681541Srgrimes case 0: 1691541Srgrimes /* existing user */ 1701541Srgrimes break; 1711541Srgrimes case 1: 1721541Srgrimes if (!defaultsetup && strcmp(auth_type, "skey") == 0) { 17386764Sjlemon fprintf(stderr, 174186119Sqingli"You must authenticate yourself before using S/Key for the first time. In\n" 1751541Srgrimes"secure mode this is normally done via an existing S/Key key. However, since\n" 176186119Sqingli"you do not have an entry in the S/Key database you will have to specify an\n" 177186500Sqingli"alternate authentication type via the `-a' flag, e.g.\n" 1781541Srgrimes" \"skeyinit -s -a passwd\"\n\n" 1791541Srgrimes"Note that entering a plaintext password over a non-secure link defeats the\n" 1801541Srgrimes"purpose of using S/Key in the fist place.\n"); 1811541Srgrimes exit(1); 1821541Srgrimes } 183122921Sandre break; 184122921Sandre } 185122921Sandre 186122921Sandre if (getuid() != 0) { 187122921Sandre if ((pp = pw_dup(pp)) == NULL) 188186119Sqingli err(1, NULL); 1895099Swollman if (!auth_userokay(pp->pw_name, auth_type, NULL, NULL)) 19018839Swollman errx(1, "Password incorrect"); 1916245Swollman } 19215652Swollman 19315652Swollman if (pledge("stdio rpath wpath cpath fattr flock tty", NULL) == -1) 19415652Swollman err(1, "pledge"); 19515652Swollman 196185747Skmacy /* Build up a default seed based on the hostname and some randomness */ 1971541Srgrimes if (gethostname(hostname, sizeof(hostname)) == -1) 198156750Sandre err(1, "gethostname"); 199156750Sandre for (i = 0, p = seed; hostname[i] && i < SKEY_NAMELEN; i++) { 200156750Sandre if (isalnum((unsigned char)hostname[i])) 201156750Sandre *p++ = tolower((unsigned char)hostname[i]); 202156750Sandre } 2031541Srgrimes for (i = 0; i < 5; i++) 2041541Srgrimes *p++ = arc4random_uniform(10) + '0'; 2051541Srgrimes *p = '\0'; 2061541Srgrimes 2071541Srgrimes /* 2081541Srgrimes * Lookup and lock the record we are about to modify. 2091541Srgrimes * If this is a new entry this will prevent other users 2101541Srgrimes * from appending new entries (and clobbering ours). 2111541Srgrimes */ 2121541Srgrimes rval = skeylookup(&skey, pp->pw_name); 2131541Srgrimes switch (rval) { 2141541Srgrimes case -1: 2151541Srgrimes err(1, "cannot open database"); 2161541Srgrimes break; 2171541Srgrimes case 0: 2181541Srgrimes /* remove user if asked to do so */ 2191541Srgrimes if (rmkey) { 2201541Srgrimes if (snprintf(filename, sizeof(filename), 2211541Srgrimes "%s/%s", _PATH_SKEYDIR, pp->pw_name) 2221541Srgrimes >= sizeof(filename)) 2231541Srgrimes errc(1, ENAMETOOLONG, 2241541Srgrimes "Cannot remove S/Key entry"); 2251541Srgrimes if (unlink(filename) != 0) 226156750Sandre err(1, "Cannot remove S/Key entry"); 227156750Sandre printf("S/Key entry for %s removed.\n", 2281541Srgrimes pp->pw_name); 2291541Srgrimes exit(0); 2301541Srgrimes } 2311541Srgrimes 2325791Swollman (void)printf("[Updating %s with %s]\n", pp->pw_name, 2331541Srgrimes ht ? ht : skey_get_algorithm()); 23451252Sru (void)printf("Old seed: [%s] %s\n", 23551252Sru skey_get_algorithm(), skey.seed); 23651252Sru 2371541Srgrimes /* 2381541Srgrimes * Sanity check old seed. 2391541Srgrimes */ 2401541Srgrimes l = strlen(skey.seed); 2411541Srgrimes for (p = skey.seed; *p; p++) { 2421541Srgrimes if (isalpha((unsigned char)*p)) { 2431541Srgrimes if (isupper((unsigned char)*p)) 2441541Srgrimes *p = tolower((unsigned char)*p); 2451541Srgrimes } else if (!isdigit((unsigned char)*p)) { 2461541Srgrimes memmove(p, p + 1, l - (p - skey.seed)); 2471541Srgrimes l--; 2481541Srgrimes } 2491541Srgrimes } 2501541Srgrimes 25121666Swollman /* If the seed ends in 0-8 just add one. */ 25221666Swollman if (l > 0) { 25389498Sru lastc = skey.seed[l - 1]; 254136155Ssam if (isdigit((unsigned char)lastc) && 2551541Srgrimes lastc != '9') { 25651252Sru (void)strlcpy(seed, skey.seed, 25751252Sru sizeof seed); 25851252Sru seed[l - 1] = lastc + 1; 2591541Srgrimes } 2601541Srgrimes if (isdigit((unsigned char)lastc) && 26151252Sru lastc == '9' && l < 16) { 2621541Srgrimes (void)strlcpy(seed, skey.seed, 2631541Srgrimes sizeof seed); 2641541Srgrimes seed[l - 1] = '0'; 2651541Srgrimes seed[l] = '0'; 2661541Srgrimes seed[l + 1] = '\0'; 2671541Srgrimes } 2681541Srgrimes } 26951252Sru break; 2701541Srgrimes case 1: 2711541Srgrimes if (rmkey) 2721541Srgrimes errx(1, "You have no entry to remove."); 2731541Srgrimes (void)printf("[Adding %s with %s]\n", pp->pw_name, 2741541Srgrimes ht ? ht : skey_get_algorithm()); 2751541Srgrimes if (snprintf(filename, sizeof(filename), "%s/%s", 2761541Srgrimes _PATH_SKEYDIR, pp->pw_name) >= sizeof(filename)) 2771541Srgrimes errc(1, ENAMETOOLONG, 2781541Srgrimes "Cannot create S/Key entry"); 2791541Srgrimes if ((l = open(filename, 2801541Srgrimes O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC |O_NOFOLLOW, 2811541Srgrimes S_IRUSR | S_IWUSR)) == -1 || 2821541Srgrimes flock(l, LOCK_EX) != 0 || 2831541Srgrimes (skey.keyfile = fdopen(l, "r+")) == NULL) 2841541Srgrimes err(1, "Cannot create S/Key entry"); 2851541Srgrimes break; 2861541Srgrimes } 2871541Srgrimes if (fchown(fileno(skey.keyfile), pp->pw_uid, -1) != 0 || 2881541Srgrimes fchmod(fileno(skey.keyfile), S_IRUSR | S_IWUSR) != 0) 2891541Srgrimes err(1, "can't set owner/mode for %s", pp->pw_name); 2901541Srgrimes if (defaultsetup && n == 0) 2911541Srgrimes n = 100; 2921541Srgrimes 2931541Srgrimes /* Set hash type if asked to */ 2941541Srgrimes if (ht && strcmp(ht, skey_get_algorithm()) != 0) 2951541Srgrimes skey_set_algorithm(ht); 29685074Sru 29785074Sru alarm(180); 29885074Sru if (!defaultsetup) 2991541Srgrimes secure_mode(&n, key, seed, sizeof seed, buf, sizeof(buf)); 3001541Srgrimes else 301128185Sluigi normal_mode(pp->pw_name, n, key, seed); 302128185Sluigi alarm(0); 303128185Sluigi 304128185Sluigi /* XXX - why use malloc here? */ 305128185Sluigi if ((skey.val = malloc(16 + 1)) == NULL) 306128185Sluigi err(1, "Can't allocate memory"); 307128185Sluigi btoa8(skey.val, key); 308128185Sluigi 309128185Sluigi (void)fseek(skey.keyfile, 0L, SEEK_SET); 310128185Sluigi (void)fprintf(skey.keyfile, "%s\n%s\n%04d\n%s\n%s\n", 311128185Sluigi pp->pw_name, skey_get_algorithm(), n, seed, skey.val); 312128185Sluigi (void)fclose(skey.keyfile); 31355205Speter 314117752Shsu (void)printf("\nID %s skey is otp-%s %d %s\n", pp->pw_name, 315120727Ssam skey_get_algorithm(), n, seed); 316120727Ssam (void)printf("Next login password: %s\n\n", 317120727Ssam hexmode ? put8(buf, key) : btoe(buf, key)); 318174934Smux exit(0); 319120727Ssam} 320120727Ssam 321120727Ssamvoid 322117752Shsusecure_mode(int *count, char *key, char *seed, size_t seedlen, 323122334Ssam char *buf, size_t bufsiz) 324122334Ssam{ 325122334Ssam char *p, newseed[SKEY_MAX_SEED_LEN + 2]; 326186119Sqingli const char *errstr; 327122334Ssam int i, n = *count; 328150130Sandre 329182801Sjulian (void)puts("You need the 6 words generated from the \"skey\" command."); 330122334Ssam if (n == 0) { 331122334Ssam for (i = 0; ; i++) { 332122334Ssam if (i >= 2) 333186119Sqingli exit(1); 334122334Ssam 335150130Sandre (void)printf("Enter sequence count from 1 to %d: ", 336122334Ssam SKEY_MAX_SEQ); 337122334Ssam (void)fgets(buf, bufsiz, stdin); 338183017Sjulian clearerr(stdin); 339183017Sjulian rip(buf); 340183017Sjulian n = strtonum(buf, 1, SKEY_MAX_SEQ-1, &errstr); 341183017Sjulian if (!errstr) 342183017Sjulian break; /* Valid range */ 343183017Sjulian fprintf(stderr, 344183017Sjulian "ERROR: Count must be between 1 and %d\n", 345183017Sjulian SKEY_MAX_SEQ - 1); 346183017Sjulian } 347182801Sjulian *count= n; 348122334Ssam } 349183017Sjulian 350183017Sjulian for (i = 0; ; i++) { 351183017Sjulian if (i >= 2) 3521541Srgrimes exit(1); 353183017Sjulian 354183017Sjulian (void)printf("Enter new seed [default %s]: ", seed); 355183017Sjulian (void)fgets(newseed, sizeof(newseed), stdin); /* XXX */ 356183017Sjulian clearerr(stdin); 357183017Sjulian rip(newseed); 358183017Sjulian if (strlen(newseed) > SKEY_MAX_SEED_LEN) { 359183017Sjulian (void)fprintf(stderr, "ERROR: Seed must be between 1 " 360183017Sjulian "and %d characters in length\n", SKEY_MAX_SEED_LEN); 361183017Sjulian continue; 362183017Sjulian } 363183017Sjulian for (p = newseed; *p; p++) { 364183017Sjulian if (isspace((unsigned char)*p)) { 365183017Sjulian (void)fputs("ERROR: Seed must not contain " 366183017Sjulian "any spaces\n", stderr); 367183017Sjulian break; 368183017Sjulian } else if (isalpha((unsigned char)*p)) { 369178898Sjulian if (isupper((unsigned char)*p)) 3701541Srgrimes *p = tolower((unsigned char)*p); 37121666Swollman } else if (!isdigit((unsigned char)*p)) { 37221666Swollman (void)fputs("ERROR: Seed must be purely " 373136155Ssam "alphanumeric\n", stderr); 37492725Salfred break; 37592725Salfred } 37692725Salfred } 37792725Salfred if (*p == '\0') 37892725Salfred break; /* Valid seed */ 37992725Salfred } 380128621Sluigi if (newseed[0] != '\0') 381128621Sluigi (void)strlcpy(seed, newseed, seedlen); 382128621Sluigi 383128621Sluigi for (i = 0; ; i++) { 384128621Sluigi if (i >= 2) 385128621Sluigi exit(1); 386128621Sluigi 387128621Sluigi (void)printf("otp-%s %d %s\nS/Key access password: ", 388128621Sluigi skey_get_algorithm(), n, seed); 389128621Sluigi (void)fgets(buf, bufsiz, stdin); 390128621Sluigi clearerr(stdin); 391128621Sluigi rip(buf); 392128621Sluigi backspace(buf); 393178888Sjulian 394178888Sjulian if (buf[0] == '?') { 395183013Sjulian (void)puts("Enter 6 words from secure S/Key calculation."); 396178888Sjulian continue; 397178888Sjulian } else if (buf[0] == '\0') 398178888Sjulian exit(1); 399178888Sjulian 400128621Sluigi if (etob(key, buf) == 1 || atob8(key, buf) == 0) 401128621Sluigi break; /* Valid format */ 402120727Ssam (void)fputs("ERROR: Invalid format - try again with the 6 words.\n", 40392725Salfred stderr); 40492725Salfred } 40592725Salfred} 406120727Ssam 40792725Salfredvoid 40892725Salfrednormal_mode(char *username, int n, char *key, char *seed) 409174559Skmacy{ 410178888Sjulian int i, nn; 411178888Sjulian char passwd[SKEY_MAX_PW_LEN+2], key2[SKEY_BINKEY_SIZE]; 412178888Sjulian 413178888Sjulian /* Get user's secret passphrase */ 414178888Sjulian for (i = 0; ; i++) { 415178888Sjulian if (i > 2) 416178888Sjulian errx(1, "S/Key entry not updated"); 417178888Sjulian 418178888Sjulian if (readpassphrase("Enter new secret passphrase: ", passwd, 419178888Sjulian sizeof(passwd), 0) == NULL || passwd[0] == '\0') 420178888Sjulian exit(1); 421178888Sjulian 422178888Sjulian if (strlen(passwd) < SKEY_MIN_PW_LEN) { 423178888Sjulian (void)fprintf(stderr, 424178888Sjulian "ERROR: Your passphrase must be at least %d " 425178888Sjulian "characters long.\n", SKEY_MIN_PW_LEN); 426178888Sjulian continue; 427178888Sjulian } else if (strcmp(passwd, username) == 0) { 428174559Skmacy (void)fputs("ERROR: Your passphrase may not be the " 429174703Skmacy "same as your user name.\n", stderr); 430174703Skmacy continue; 431174703Skmacy } else if (strspn(passwd, "abcdefghijklmnopqrstuvwxyz") == 432174703Skmacy strlen(passwd)) { 4331541Srgrimes (void)fputs("ERROR: Your passphrase must contain more " 4342168Spaul "than just lower case letters.\nWhitespace, " 4352168Spaul "numbers, and punctuation are suggested.\n", 436 stderr); 437 continue; 438 } else if (strlen(passwd) > 63) { 439 (void)fprintf(stderr, "WARNING: Your passphrase is " 440 "longer than the recommended maximum length of 63\n"); 441 } 442 /* XXX - should check for passphrase that is really too long */ 443 444 /* Crunch seed and passphrase into starting key */ 445 nn = keycrunch(key, seed, passwd); 446 explicit_bzero(passwd, sizeof(passwd)); 447 if (nn != 0) 448 err(2, "key crunch failed"); 449 450 if (readpassphrase("Again secret passphrase: ", passwd, 451 sizeof(passwd), 0) == NULL || passwd[0] == '\0') 452 exit(1); 453 454 /* Crunch seed and passphrase into starting key */ 455 nn = keycrunch(key2, seed, passwd); 456 explicit_bzero(passwd, sizeof(passwd)); 457 if (nn != 0) 458 err(2, "key crunch failed"); 459 460 if (memcmp(key, key2, sizeof(key2)) == 0) 461 break; 462 463 (void)fputs("Passphrases do not match.\n", stderr); 464 } 465 466 nn = n; 467 while (nn-- != 0) 468 f(key); 469} 470 471void 472enable_db(int op) 473{ 474 if (op == 1) { 475 /* enable */ 476 if (mkdir(_PATH_SKEYDIR, 01730) != 0 && errno != EEXIST) 477 err(1, "can't mkdir %s", _PATH_SKEYDIR); 478 if (chown(_PATH_SKEYDIR, geteuid(), getegid()) != 0) 479 err(1, "can't chown %s", _PATH_SKEYDIR); 480 if (chmod(_PATH_SKEYDIR, 01730) != 0) 481 err(1, "can't chmod %s", _PATH_SKEYDIR); 482 } else { 483 /* disable */ 484 if (chmod(_PATH_SKEYDIR, 0) != 0 && errno != ENOENT) 485 err(1, "can't chmod %s", _PATH_SKEYDIR); 486 } 487} 488 489void 490usage(void) 491{ 492 extern char *__progname; 493 494 (void)fprintf(stderr, "usage: %s [-DErsx] [-a auth-type] [-n count]" 495 "\n\t[-md5 | -rmd160 | -sha1] [user]\n", __progname); 496 exit(1); 497} 498