1 /* $OpenBSD: skeyinit.c,v 1.72 2016/05/17 23:36:29 tb 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/file.h> 16 #include <sys/resource.h> 17 #include <sys/stat.h> 18 #include <sys/time.h> 19 20 #include <ctype.h> 21 #include <err.h> 22 #include <errno.h> 23 #include <pwd.h> 24 #include <readpassphrase.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <syslog.h> 29 #include <time.h> 30 #include <unistd.h> 31 #include <limits.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 enable_db(int); 45 46 int 47 main(int argc, char **argv) 48 { 49 int rval, i, l, n, defaultsetup, rmkey, hexmode, enable; 50 char hostname[HOST_NAME_MAX+1]; 51 char seed[SKEY_MAX_SEED_LEN + 1]; 52 char buf[256], key[SKEY_BINKEY_SIZE], filename[PATH_MAX], *ht; 53 char lastc, *p, *auth_type; 54 const char *errstr; 55 struct skey skey; 56 struct passwd *pp; 57 58 n = rmkey = hexmode = enable = 0; 59 defaultsetup = 1; 60 ht = auth_type = NULL; 61 62 for (i = 1; i < argc && argv[i][0] == '-' && strcmp(argv[i], "--");) { 63 if (argv[i][2] == '\0') { 64 /* Single character switch */ 65 switch (argv[i][1]) { 66 case 'a': 67 if (argv[++i] == NULL || argv[i][0] == '\0') 68 usage(); 69 auth_type = argv[i]; 70 break; 71 case 's': 72 defaultsetup = 0; 73 if (auth_type == NULL) 74 auth_type = "skey"; 75 break; 76 case 'x': 77 hexmode = 1; 78 break; 79 case 'r': 80 rmkey = 1; 81 break; 82 case 'n': 83 if (argv[++i] == NULL || argv[i][0] == '\0') 84 usage(); 85 n = strtonum(argv[i], 1, SKEY_MAX_SEQ - 1, &errstr); 86 if (errstr) 87 errx(1, "count must be > 0 and < %d", 88 SKEY_MAX_SEQ); 89 break; 90 case 'D': 91 enable = -1; 92 break; 93 case 'E': 94 enable = 1; 95 break; 96 default: 97 usage(); 98 } 99 } else { 100 /* Multi character switches are hash types */ 101 if ((ht = skey_set_algorithm(&argv[i][1])) == NULL) { 102 warnx("Unknown hash algorithm %s", &argv[i][1]); 103 usage(); 104 } 105 } 106 i++; 107 } 108 argv += i; 109 argc -= i; 110 111 if (argc > 1 || (enable && argc)) 112 usage(); 113 114 /* Handle -D and -E */ 115 if (enable) { 116 enable_db(enable); 117 exit(0); 118 } 119 120 if (getuid() != 0) { 121 if (pledge("stdio rpath wpath cpath fattr flock tty proc exec " 122 "getpw", NULL) == -1) 123 err(1, "pledge"); 124 125 if ((pp = getpwuid(getuid())) == NULL) 126 err(1, "no user with uid %u", getuid()); 127 128 if (argc == 1) { 129 char me[UT_NAMESIZE + 1]; 130 131 (void)strlcpy(me, pp->pw_name, sizeof me); 132 if ((pp = getpwnam(argv[0])) == NULL) 133 errx(1, "User unknown: %s", argv[0]); 134 if (strcmp(pp->pw_name, me) != 0) 135 errx(1, "Permission denied."); 136 } 137 } else { 138 if (pledge("stdio rpath wpath cpath fattr flock tty getpw id", 139 NULL) == -1) 140 err(1, "pledge"); 141 142 if (argc == 1) { 143 if ((pp = getpwnam(argv[0])) == NULL) { 144 static struct passwd _pp; 145 146 _pp.pw_name = argv[0]; 147 pp = &_pp; 148 warnx("Warning, user unknown: %s", argv[0]); 149 } else { 150 /* So the file ends up owned by the proper ID */ 151 if (setresuid(-1, pp->pw_uid, -1) != 0) 152 errx(1, "unable to change uid to %u", 153 pp->pw_uid); 154 } 155 } else if ((pp = getpwuid(0)) == NULL) 156 err(1, "no user with uid 0"); 157 158 if (pledge("stdio rpath wpath cpath fattr flock tty", NULL) 159 == -1) 160 err(1, "pledge"); 161 } 162 163 switch (skey_haskey(pp->pw_name)) { 164 case -1: 165 if (errno == ENOENT || errno == EPERM) 166 errx(1, "S/Key disabled"); 167 else 168 err(1, "cannot open database"); 169 break; 170 case 0: 171 /* existing user */ 172 break; 173 case 1: 174 if (!defaultsetup && strcmp(auth_type, "skey") == 0) { 175 fprintf(stderr, 176 "You must authenticate yourself before using S/Key for the first time. In\n" 177 "secure mode this is normally done via an existing S/Key key. However, since\n" 178 "you do not have an entry in the S/Key database you will have to specify an\n" 179 "alternate authentication type via the `-a' flag, e.g.\n" 180 " \"skeyinit -s -a passwd\"\n\n" 181 "Note that entering a plaintext password over a non-secure link defeats the\n" 182 "purpose of using S/Key in the fist place.\n"); 183 exit(1); 184 } 185 break; 186 } 187 188 if (getuid() != 0) { 189 if ((pp = pw_dup(pp)) == NULL) 190 err(1, NULL); 191 if (!auth_userokay(pp->pw_name, auth_type, NULL, NULL)) 192 errx(1, "Password incorrect"); 193 } 194 195 if (pledge("stdio rpath wpath cpath fattr flock tty", NULL) == -1) 196 err(1, "pledge"); 197 198 /* Build up a default seed based on the hostname and some randomness */ 199 if (gethostname(hostname, sizeof(hostname)) < 0) 200 err(1, "gethostname"); 201 for (i = 0, p = seed; hostname[i] && i < SKEY_NAMELEN; i++) { 202 if (isalnum((unsigned char)hostname[i])) 203 *p++ = tolower((unsigned char)hostname[i]); 204 } 205 for (i = 0; i < 5; i++) 206 *p++ = arc4random_uniform(10) + '0'; 207 *p = '\0'; 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 errc(1, ENAMETOOLONG, 226 "Cannot remove S/Key entry"); 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((unsigned char)*p)) { 245 if (isupper((unsigned char)*p)) 246 *p = tolower((unsigned char)*p); 247 } else if (!isdigit((unsigned char)*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((unsigned char)lastc) && 257 lastc != '9') { 258 (void)strlcpy(seed, skey.seed, 259 sizeof seed); 260 seed[l - 1] = lastc + 1; 261 } 262 if (isdigit((unsigned char)lastc) && 263 lastc == '9' && l < 16) { 264 (void)strlcpy(seed, skey.seed, 265 sizeof seed); 266 seed[l - 1] = '0'; 267 seed[l] = '0'; 268 seed[l + 1] = '\0'; 269 } 270 } 271 break; 272 case 1: 273 if (rmkey) 274 errx(1, "You have no entry to remove."); 275 (void)printf("[Adding %s with %s]\n", pp->pw_name, 276 ht ? ht : skey_get_algorithm()); 277 if (snprintf(filename, sizeof(filename), "%s/%s", 278 _PATH_SKEYDIR, pp->pw_name) >= sizeof(filename)) 279 errc(1, ENAMETOOLONG, 280 "Cannot create S/Key entry"); 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 (defaultsetup && n == 0) 293 n = 100; 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 = 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 const char *errstr; 329 int i, n = *count; 330 331 (void)puts("You need the 6 words generated from the \"skey\" command."); 332 if (n == 0) { 333 for (i = 0; ; i++) { 334 if (i >= 2) 335 exit(1); 336 337 (void)printf("Enter sequence count from 1 to %d: ", 338 SKEY_MAX_SEQ); 339 (void)fgets(buf, bufsiz, stdin); 340 clearerr(stdin); 341 rip(buf); 342 n = strtonum(buf, 1, SKEY_MAX_SEQ-1, &errstr); 343 if (!errstr) 344 break; /* Valid range */ 345 fprintf(stderr, 346 "ERROR: Count must be between 1 and %d\n", 347 SKEY_MAX_SEQ - 1); 348 } 349 *count= n; 350 } 351 352 for (i = 0; ; i++) { 353 if (i >= 2) 354 exit(1); 355 356 (void)printf("Enter new seed [default %s]: ", seed); 357 (void)fgets(newseed, sizeof(newseed), stdin); /* XXX */ 358 clearerr(stdin); 359 rip(newseed); 360 if (strlen(newseed) > SKEY_MAX_SEED_LEN) { 361 (void)fprintf(stderr, "ERROR: Seed must be between 1 " 362 "and %d characters in length\n", SKEY_MAX_SEED_LEN); 363 continue; 364 } 365 for (p = newseed; *p; p++) { 366 if (isspace((unsigned char)*p)) { 367 (void)fputs("ERROR: Seed must not contain " 368 "any spaces\n", stderr); 369 break; 370 } else if (isalpha((unsigned char)*p)) { 371 if (isupper((unsigned char)*p)) 372 *p = tolower((unsigned char)*p); 373 } else if (!isdigit((unsigned char)*p)) { 374 (void)fputs("ERROR: Seed must be purely " 375 "alphanumeric\n", stderr); 376 break; 377 } 378 } 379 if (*p == '\0') 380 break; /* Valid seed */ 381 } 382 if (newseed[0] != '\0') 383 (void)strlcpy(seed, newseed, seedlen); 384 385 for (i = 0; ; i++) { 386 if (i >= 2) 387 exit(1); 388 389 (void)printf("otp-%s %d %s\nS/Key access password: ", 390 skey_get_algorithm(), n, seed); 391 (void)fgets(buf, bufsiz, stdin); 392 clearerr(stdin); 393 rip(buf); 394 backspace(buf); 395 396 if (buf[0] == '?') { 397 (void)puts("Enter 6 words from secure S/Key calculation."); 398 continue; 399 } else if (buf[0] == '\0') 400 exit(1); 401 402 if (etob(key, buf) == 1 || atob8(key, buf) == 0) 403 break; /* Valid format */ 404 (void)fputs("ERROR: Invalid format - try again with the 6 words.\n", 405 stderr); 406 } 407 } 408 409 void 410 normal_mode(char *username, int n, char *key, char *seed) 411 { 412 int i, nn; 413 char passwd[SKEY_MAX_PW_LEN+2], key2[SKEY_BINKEY_SIZE]; 414 415 /* Get user's secret passphrase */ 416 for (i = 0; ; i++) { 417 if (i > 2) 418 errx(1, "S/Key entry not updated"); 419 420 if (readpassphrase("Enter new secret passphrase: ", passwd, 421 sizeof(passwd), 0) == NULL || passwd[0] == '\0') 422 exit(1); 423 424 if (strlen(passwd) < SKEY_MIN_PW_LEN) { 425 (void)fprintf(stderr, 426 "ERROR: Your passphrase must be at least %d " 427 "characters long.\n", SKEY_MIN_PW_LEN); 428 continue; 429 } else if (strcmp(passwd, username) == 0) { 430 (void)fputs("ERROR: Your passphrase may not be the " 431 "same as your user name.\n", stderr); 432 continue; 433 } else if (strspn(passwd, "abcdefghijklmnopqrstuvwxyz") == 434 strlen(passwd)) { 435 (void)fputs("ERROR: Your passphrase must contain more " 436 "than just lower case letters.\nWhitespace, " 437 "numbers, and punctuation are suggested.\n", 438 stderr); 439 continue; 440 } else if (strlen(passwd) > 63) { 441 (void)fprintf(stderr, "WARNING: Your passphrase is " 442 "longer than the recommended maximum length of 63\n"); 443 } 444 /* XXX - should check for passphrase that is really too long */ 445 446 /* Crunch seed and passphrase into starting key */ 447 nn = keycrunch(key, seed, passwd); 448 explicit_bzero(passwd, sizeof(passwd)); 449 if (nn != 0) 450 err(2, "key crunch failed"); 451 452 if (readpassphrase("Again secret passphrase: ", passwd, 453 sizeof(passwd), 0) == NULL || passwd[0] == '\0') 454 exit(1); 455 456 /* Crunch seed and passphrase into starting key */ 457 nn = keycrunch(key2, seed, passwd); 458 explicit_bzero(passwd, sizeof(passwd)); 459 if (nn != 0) 460 err(2, "key crunch failed"); 461 462 if (memcmp(key, key2, sizeof(key2)) == 0) 463 break; 464 465 (void)fputs("Passphrases do not match.\n", stderr); 466 } 467 468 nn = n; 469 while (nn-- != 0) 470 f(key); 471 } 472 473 void 474 enable_db(int op) 475 { 476 if (op == 1) { 477 /* enable */ 478 if (mkdir(_PATH_SKEYDIR, 01730) != 0 && errno != EEXIST) 479 err(1, "can't mkdir %s", _PATH_SKEYDIR); 480 if (chown(_PATH_SKEYDIR, geteuid(), getegid()) != 0) 481 err(1, "can't chown %s", _PATH_SKEYDIR); 482 if (chmod(_PATH_SKEYDIR, 01730) != 0) 483 err(1, "can't chmod %s", _PATH_SKEYDIR); 484 } else { 485 /* disable */ 486 if (chmod(_PATH_SKEYDIR, 0) != 0 && errno != ENOENT) 487 err(1, "can't chmod %s", _PATH_SKEYDIR); 488 } 489 } 490 491 void 492 usage(void) 493 { 494 extern char *__progname; 495 496 (void)fprintf(stderr, "usage: %s [-DErsx] [-a auth-type] [-n count]" 497 "\n\t[-md5 | -rmd160 | -sha1] [user]\n", __progname); 498 exit(1); 499 } 500