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
39 void usage(void);
40 void secure_mode(int *, char *, char *, size_t, char *, size_t);
41 void normal_mode(char *, int, char *, char *);
42 void enable_db(int);
43
44 int
main(int argc,char ** argv)45 main(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
321 void
secure_mode(int * count,char * key,char * seed,size_t seedlen,char * buf,size_t bufsiz)322 secure_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
407 void
normal_mode(char * username,int n,char * key,char * seed)408 normal_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
471 void
enable_db(int op)472 enable_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
489 void
usage(void)490 usage(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