1/* $OpenBSD: skeyinit.c,v 1.75 2019/06/28 13:35:03 deraadt 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 <millert@openbsd.org> 11 * 12 * S/Key initialization and seed update 13 */ 14 15#include <sys/stat.h> 16 17#include <ctype.h> 18#include <err.h> 19#include <errno.h> 20#include <fcntl.h> 21#include <pwd.h> 22#include <readpassphrase.h> 23#include <stdio.h> 24#include <stdlib.h> 25#include <string.h> 26#include <syslog.h> 27#include <time.h> 28#include <unistd.h> 29#include <limits.h> 30#include <utmp.h> 31 32#include <skey.h> 33#include <bsd_auth.h> 34 35#ifndef SKEY_NAMELEN 36#define SKEY_NAMELEN 4 37#endif 38 39void usage(void); 40void secure_mode(int *, char *, char *, size_t, char *, size_t); 41void normal_mode(char *, int, char *, char *); 42void enable_db(int); 43 44int 45main(int argc, char **argv) 46{ 47 int rval, i, l, n, defaultsetup, rmkey, hexmode, enable; 48 char hostname[HOST_NAME_MAX+1]; 49 char seed[SKEY_MAX_SEED_LEN + 1]; 50 char buf[256], key[SKEY_BINKEY_SIZE], filename[PATH_MAX], *ht; 51 char lastc, *p, *auth_type; 52 const char *errstr; 53 struct skey skey; 54 struct passwd *pp; 55 56 n = rmkey = hexmode = enable = 0; 57 defaultsetup = 1; 58 ht = auth_type = NULL; 59 60 for (i = 1; i < argc && argv[i][0] == '-' && strcmp(argv[i], "--");) { 61 if (argv[i][2] == '\0') { 62 /* Single character switch */ 63 switch (argv[i][1]) { 64 case 'a': 65 if (argv[++i] == NULL || argv[i][0] == '\0') 66 usage(); 67 auth_type = argv[i]; 68 break; 69 case 's': 70 defaultsetup = 0; 71 if (auth_type == NULL) 72 auth_type = "skey"; 73 break; 74 case 'x': 75 hexmode = 1; 76 break; 77 case 'r': 78 rmkey = 1; 79 break; 80 case 'n': 81 if (argv[++i] == NULL || argv[i][0] == '\0') 82 usage(); 83 n = strtonum(argv[i], 1, SKEY_MAX_SEQ - 1, &errstr); 84 if (errstr) 85 errx(1, "count must be > 0 and < %d", 86 SKEY_MAX_SEQ); 87 break; 88 case 'D': 89 enable = -1; 90 break; 91 case 'E': 92 enable = 1; 93 break; 94 default: 95 usage(); 96 } 97 } else { 98 /* Multi character switches are hash types */ 99 if ((ht = skey_set_algorithm(&argv[i][1])) == NULL) { 100 warnx("Unknown hash algorithm %s", &argv[i][1]); 101 usage(); 102 } 103 } 104 i++; 105 } 106 argv += i; 107 argc -= i; 108 109 if (argc > 1 || (enable && argc)) 110 usage(); 111 112 /* Handle -D and -E */ 113 if (enable) { 114 enable_db(enable); 115 exit(0); 116 } 117 118 if (getuid() != 0) { 119 if (pledge("stdio rpath wpath cpath fattr flock tty proc exec " 120 "getpw", NULL) == -1) 121 err(1, "pledge"); 122 123 if ((pp = getpwuid(getuid())) == NULL) 124 err(1, "no user with uid %u", getuid()); 125 126 if (argc == 1) { 127 char me[UT_NAMESIZE + 1]; 128 129 (void)strlcpy(me, pp->pw_name, sizeof me); 130 if ((pp = getpwnam(argv[0])) == NULL) 131 errx(1, "User unknown: %s", argv[0]); 132 if (strcmp(pp->pw_name, me) != 0) 133 errx(1, "Permission denied."); 134 } 135 } else { 136 if (pledge("stdio rpath wpath cpath fattr flock tty getpw id", 137 NULL) == -1) 138 err(1, "pledge"); 139 140 if (argc == 1) { 141 if ((pp = getpwnam(argv[0])) == NULL) { 142 static struct passwd _pp; 143 144 _pp.pw_name = argv[0]; 145 pp = &_pp; 146 warnx("Warning, user unknown: %s", argv[0]); 147 } else { 148 /* So the file ends up owned by the proper ID */ 149 if (setresuid(-1, pp->pw_uid, -1) != 0) 150 errx(1, "unable to change uid to %u", 151 pp->pw_uid); 152 } 153 } else if ((pp = getpwuid(0)) == NULL) 154 err(1, "no user with uid 0"); 155 156 if (pledge("stdio rpath wpath cpath fattr flock tty", NULL) 157 == -1) 158 err(1, "pledge"); 159 } 160 161 switch (skey_haskey(pp->pw_name)) { 162 case -1: 163 if (errno == ENOENT || errno == EPERM) 164 errx(1, "S/Key disabled"); 165 else 166 err(1, "cannot open database"); 167 break; 168 case 0: 169 /* existing user */ 170 break; 171 case 1: 172 if (!defaultsetup && strcmp(auth_type, "skey") == 0) { 173 fprintf(stderr, 174"You must authenticate yourself before using S/Key for the first time. In\n" 175"secure mode this is normally done via an existing S/Key key. However, since\n" 176"you do not have an entry in the S/Key database you will have to specify an\n" 177"alternate authentication type via the `-a' flag, e.g.\n" 178" \"skeyinit -s -a passwd\"\n\n" 179"Note that entering a plaintext password over a non-secure link defeats the\n" 180"purpose of using S/Key in the fist place.\n"); 181 exit(1); 182 } 183 break; 184 } 185 186 if (getuid() != 0) { 187 if ((pp = pw_dup(pp)) == NULL) 188 err(1, NULL); 189 if (!auth_userokay(pp->pw_name, auth_type, NULL, NULL)) 190 errx(1, "Password incorrect"); 191 } 192 193 if (pledge("stdio rpath wpath cpath fattr flock tty", NULL) == -1) 194 err(1, "pledge"); 195 196 /* Build up a default seed based on the hostname and some randomness */ 197 if (gethostname(hostname, sizeof(hostname)) == -1) 198 err(1, "gethostname"); 199 for (i = 0, p = seed; hostname[i] && i < SKEY_NAMELEN; i++) { 200 if (isalnum((unsigned char)hostname[i])) 201 *p++ = tolower((unsigned char)hostname[i]); 202 } 203 for (i = 0; i < 5; i++) 204 *p++ = arc4random_uniform(10) + '0'; 205 *p = '\0'; 206 207 /* 208 * Lookup and lock the record we are about to modify. 209 * If this is a new entry this will prevent other users 210 * from appending new entries (and clobbering ours). 211 */ 212 rval = skeylookup(&skey, pp->pw_name); 213 switch (rval) { 214 case -1: 215 err(1, "cannot open database"); 216 break; 217 case 0: 218 /* remove user if asked to do so */ 219 if (rmkey) { 220 if (snprintf(filename, sizeof(filename), 221 "%s/%s", _PATH_SKEYDIR, pp->pw_name) 222 >= sizeof(filename)) 223 errc(1, ENAMETOOLONG, 224 "Cannot remove S/Key entry"); 225 if (unlink(filename) != 0) 226 err(1, "Cannot remove S/Key entry"); 227 printf("S/Key entry for %s removed.\n", 228 pp->pw_name); 229 exit(0); 230 } 231 232 (void)printf("[Updating %s with %s]\n", pp->pw_name, 233 ht ? ht : skey_get_algorithm()); 234 (void)printf("Old seed: [%s] %s\n", 235 skey_get_algorithm(), skey.seed); 236 237 /* 238 * Sanity check old seed. 239 */ 240 l = strlen(skey.seed); 241 for (p = skey.seed; *p; p++) { 242 if (isalpha((unsigned char)*p)) { 243 if (isupper((unsigned char)*p)) 244 *p = tolower((unsigned char)*p); 245 } else if (!isdigit((unsigned char)*p)) { 246 memmove(p, p + 1, l - (p - skey.seed)); 247 l--; 248 } 249 } 250 251 /* If the seed ends in 0-8 just add one. */ 252 if (l > 0) { 253 lastc = skey.seed[l - 1]; 254 if (isdigit((unsigned char)lastc) && 255 lastc != '9') { 256 (void)strlcpy(seed, skey.seed, 257 sizeof seed); 258 seed[l - 1] = lastc + 1; 259 } 260 if (isdigit((unsigned char)lastc) && 261 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 errc(1, ENAMETOOLONG, 278 "Cannot create S/Key entry"); 279 if ((l = open(filename, 280 O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC |O_NOFOLLOW, 281 S_IRUSR | S_IWUSR)) == -1 || 282 flock(l, LOCK_EX) != 0 || 283 (skey.keyfile = fdopen(l, "r+")) == NULL) 284 err(1, "Cannot create S/Key entry"); 285 break; 286 } 287 if (fchown(fileno(skey.keyfile), pp->pw_uid, -1) != 0 || 288 fchmod(fileno(skey.keyfile), S_IRUSR | S_IWUSR) != 0) 289 err(1, "can't set owner/mode for %s", pp->pw_name); 290 if (defaultsetup && n == 0) 291 n = 100; 292 293 /* Set hash type if asked to */ 294 if (ht && strcmp(ht, skey_get_algorithm()) != 0) 295 skey_set_algorithm(ht); 296 297 alarm(180); 298 if (!defaultsetup) 299 secure_mode(&n, key, seed, sizeof seed, buf, sizeof(buf)); 300 else 301 normal_mode(pp->pw_name, n, key, seed); 302 alarm(0); 303 304 /* XXX - why use malloc here? */ 305 if ((skey.val = malloc(16 + 1)) == NULL) 306 err(1, "Can't allocate memory"); 307 btoa8(skey.val, key); 308 309 (void)fseek(skey.keyfile, 0L, SEEK_SET); 310 (void)fprintf(skey.keyfile, "%s\n%s\n%04d\n%s\n%s\n", 311 pp->pw_name, skey_get_algorithm(), n, seed, skey.val); 312 (void)fclose(skey.keyfile); 313 314 (void)printf("\nID %s skey is otp-%s %d %s\n", pp->pw_name, 315 skey_get_algorithm(), n, seed); 316 (void)printf("Next login password: %s\n\n", 317 hexmode ? put8(buf, key) : btoe(buf, key)); 318 exit(0); 319} 320 321void 322secure_mode(int *count, char *key, char *seed, size_t seedlen, 323 char *buf, size_t bufsiz) 324{ 325 char *p, newseed[SKEY_MAX_SEED_LEN + 2]; 326 const char *errstr; 327 int i, n = *count; 328 329 (void)puts("You need the 6 words generated from the \"skey\" command."); 330 if (n == 0) { 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 rip(buf); 340 n = strtonum(buf, 1, SKEY_MAX_SEQ-1, &errstr); 341 if (!errstr) 342 break; /* Valid range */ 343 fprintf(stderr, 344 "ERROR: Count must be between 1 and %d\n", 345 SKEY_MAX_SEQ - 1); 346 } 347 *count= n; 348 } 349 350 for (i = 0; ; i++) { 351 if (i >= 2) 352 exit(1); 353 354 (void)printf("Enter new seed [default %s]: ", seed); 355 (void)fgets(newseed, sizeof(newseed), stdin); /* XXX */ 356 clearerr(stdin); 357 rip(newseed); 358 if (strlen(newseed) > SKEY_MAX_SEED_LEN) { 359 (void)fprintf(stderr, "ERROR: Seed must be between 1 " 360 "and %d characters in length\n", SKEY_MAX_SEED_LEN); 361 continue; 362 } 363 for (p = newseed; *p; p++) { 364 if (isspace((unsigned char)*p)) { 365 (void)fputs("ERROR: Seed must not contain " 366 "any spaces\n", stderr); 367 break; 368 } else if (isalpha((unsigned char)*p)) { 369 if (isupper((unsigned char)*p)) 370 *p = tolower((unsigned char)*p); 371 } else if (!isdigit((unsigned char)*p)) { 372 (void)fputs("ERROR: Seed must be purely " 373 "alphanumeric\n", stderr); 374 break; 375 } 376 } 377 if (*p == '\0') 378 break; /* Valid seed */ 379 } 380 if (newseed[0] != '\0') 381 (void)strlcpy(seed, newseed, seedlen); 382 383 for (i = 0; ; i++) { 384 if (i >= 2) 385 exit(1); 386 387 (void)printf("otp-%s %d %s\nS/Key access password: ", 388 skey_get_algorithm(), n, seed); 389 (void)fgets(buf, bufsiz, stdin); 390 clearerr(stdin); 391 rip(buf); 392 backspace(buf); 393 394 if (buf[0] == '?') { 395 (void)puts("Enter 6 words from secure S/Key calculation."); 396 continue; 397 } else if (buf[0] == '\0') 398 exit(1); 399 400 if (etob(key, buf) == 1 || atob8(key, buf) == 0) 401 break; /* Valid format */ 402 (void)fputs("ERROR: Invalid format - try again with the 6 words.\n", 403 stderr); 404 } 405} 406 407void 408normal_mode(char *username, int n, char *key, char *seed) 409{ 410 int i, nn; 411 char passwd[SKEY_MAX_PW_LEN+2], key2[SKEY_BINKEY_SIZE]; 412 413 /* Get user's secret passphrase */ 414 for (i = 0; ; i++) { 415 if (i > 2) 416 errx(1, "S/Key entry not updated"); 417 418 if (readpassphrase("Enter new secret passphrase: ", passwd, 419 sizeof(passwd), 0) == NULL || passwd[0] == '\0') 420 exit(1); 421 422 if (strlen(passwd) < SKEY_MIN_PW_LEN) { 423 (void)fprintf(stderr, 424 "ERROR: Your passphrase must be at least %d " 425 "characters long.\n", SKEY_MIN_PW_LEN); 426 continue; 427 } else if (strcmp(passwd, username) == 0) { 428 (void)fputs("ERROR: Your passphrase may not be the " 429 "same as your user name.\n", stderr); 430 continue; 431 } else if (strspn(passwd, "abcdefghijklmnopqrstuvwxyz") == 432 strlen(passwd)) { 433 (void)fputs("ERROR: Your passphrase must contain more " 434 "than just lower case letters.\nWhitespace, " 435 "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