xref: /openbsd-src/usr.bin/skeyinit/skeyinit.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /*	$OpenBSD: skeyinit.c,v 1.44 2003/05/07 20:39:29 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 <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
185 secure mode this is normally done via an existing S/Key key.  However, since
186 you do not have an entry in the S/Key database you will have to specify an
187 alternate authentication type via the `-a' flag, e.g.
188     \"skeyinit -s -a krb5\" or \"skeyinit -s -a passwd\"\n
189 Note that entering a plaintext password over a non-secure link defeats the
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