xref: /openbsd-src/usr.bin/skeyinit/skeyinit.c (revision 898184e3e61f9129feb5978fad5a8c6865f00b92)
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