version 1.27, 2001/01/26 16:27:04 |
version 1.28, 2001/06/20 22:25:08 |
|
|
/* $OpenBSD$ */ |
/* $OpenBSD$ */ |
|
|
/* S/KEY v1.1b (skeyinit.c) |
/* OpenBSD S/Key (skeyinit.c) |
* |
* |
* Authors: |
* Authors: |
* Neil M. Haller <nmh@thumper.bellcore.com> |
* Neil M. Haller <nmh@thumper.bellcore.com> |
* Philip R. Karn <karn@chicago.qualcomm.com> |
* Philip R. Karn <karn@chicago.qualcomm.com> |
* John S. Walden <jsw@thumper.bellcore.com> |
* John S. Walden <jsw@thumper.bellcore.com> |
* Scott Chasin <chasin@crimelab.com> |
* Scott Chasin <chasin@crimelab.com> |
* |
|
* Modifications: |
|
* Todd C. Miller <Todd.Miller@courtesan.com> |
* Todd C. Miller <Todd.Miller@courtesan.com> |
* |
* |
* S/KEY initialization and seed update |
* S/Key initialization and seed update |
*/ |
*/ |
|
|
#include <sys/param.h> |
#include <sys/param.h> |
|
|
#include <errno.h> |
#include <errno.h> |
#include <ctype.h> |
#include <ctype.h> |
#include <pwd.h> |
#include <pwd.h> |
|
#include <readpassphrase.h> |
#include <stdio.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <string.h> |
#include <string.h> |
|
|
#define SKEY_NAMELEN 4 |
#define SKEY_NAMELEN 4 |
#endif |
#endif |
|
|
|
void lockeof __P((struct skey *, char *)); |
void usage __P((char *)); |
void usage __P((char *)); |
|
void secure_mode __P((int *, char *, char *, char *, char *, size_t)); |
|
void normal_mode __P((char *, int, char *, char *, char *)); |
|
void timedout __P((int)); |
|
|
int |
int |
main(argc, argv) |
main(argc, argv) |
int argc; |
int argc; |
char *argv[]; |
char *argv[]; |
{ |
{ |
int rval, nn, i, l, n=0, defaultsetup=1, zerokey=0, hexmode=0; |
int rval, i, l, n=0, defaultsetup=1, zerokey=0, hexmode=0; |
|
int oldmd4=0; |
time_t now; |
time_t now; |
|
size_t seedlen; |
char hostname[MAXHOSTNAMELEN]; |
char hostname[MAXHOSTNAMELEN]; |
char passwd[SKEY_MAX_PW_LEN+2], passwd2[SKEY_MAX_PW_LEN+2]; |
char passwd[SKEY_MAX_PW_LEN+2]; |
char seed[SKEY_MAX_SEED_LEN+2], defaultseed[SKEY_MAX_SEED_LEN+1]; |
char seed[SKEY_MAX_SEED_LEN+2], defaultseed[SKEY_MAX_SEED_LEN+1]; |
char tbuf[27], buf[80], key[SKEY_BINKEY_SIZE]; |
char tbuf[27], buf[256], key[SKEY_BINKEY_SIZE]; |
char lastc, me[UT_NAMESIZE+1], *salt, *p, *pw, *ht=NULL; |
char lastc, me[UT_NAMESIZE+1], *salt, *p, *ht=NULL; |
struct skey skey; |
struct skey skey; |
struct passwd *pp; |
struct passwd *pp; |
struct tm *tm; |
struct tm *tm; |
|
|
if (geteuid() != 0) |
if (geteuid() != 0) |
errx(1, "must be setuid root."); |
errx(1, "must be setuid root."); |
|
|
|
/* Build up a default seed based on the hostname and time */ |
if (gethostname(hostname, sizeof(hostname)) < 0) |
if (gethostname(hostname, sizeof(hostname)) < 0) |
err(1, "gethostname"); |
err(1, "gethostname"); |
for (i = 0, p = defaultseed; hostname[i] && i < SKEY_NAMELEN; i++) { |
for (i = 0, p = defaultseed; hostname[i] && i < SKEY_NAMELEN; i++) { |
|
|
} |
} |
} |
} |
|
|
|
if (defaultsetup) |
|
fputs("Reminder - Only use this method if you are directly connected\n or have an encrypted channel. If you are using telnet\n or rlogin, hit return now and use skeyinit -s.\n", stderr); |
|
|
if (getuid() != 0) { |
if (getuid() != 0) { |
pw = getpass("Password (or `s/key'):"); |
/* XXX - use BSD auth */ |
if (strcasecmp(pw, "s/key") == 0) { |
passwd[0] = '\0'; |
if (skey_haskey(me)) |
if (!defaultsetup && skeychallenge(&skey, me, buf) == 0) { |
exit(1); |
printf("Enter S/Key password below or hit return twice " |
if (skey_authenticate(me)) |
"to enter standard password.\n%s\n", buf); |
|
fflush(stdout); |
|
if (!readpassphrase("S/Key Password: ", passwd, |
|
sizeof(passwd), 0) || passwd[0] == '\0') { |
|
readpassphrase("S/Key Password: [echo on] ", |
|
passwd, sizeof(passwd), RPP_ECHO_ON); |
|
} |
|
} |
|
if (passwd[0]) { |
|
if (skeyverify(&skey, passwd) != 0) |
errx(1, "Password incorrect."); |
errx(1, "Password incorrect."); |
} else { |
} else { |
p = crypt(pw, salt); |
fflush(stdout); |
if (strcmp(p, pp->pw_passwd)) |
readpassphrase("Password: ", passwd, sizeof(passwd), 0); |
errx(1, "Password incorrect."); |
if (strcmp(crypt(passwd, salt), pp->pw_passwd)) { |
|
if (passwd[0]) |
|
warnx("Password incorrect."); |
|
exit(1); |
|
} |
} |
} |
} |
} |
|
|
|
/* |
|
* Lookup and lock the record we are about to modify. |
|
* If this is a new entry this will prevent other users |
|
* from appending new entries (and clobbering ours). |
|
*/ |
rval = skeylookup(&skey, pp->pw_name); |
rval = skeylookup(&skey, pp->pw_name); |
switch (rval) { |
switch (rval) { |
case -1: |
case -1: |
|
|
if (zerokey) |
if (zerokey) |
exit(skeyzero(&skey, pp->pw_name)); |
exit(skeyzero(&skey, pp->pw_name)); |
|
|
(void)printf("[Updating %s]\n", pp->pw_name); |
(void)printf("[Updating %s with %s]\n", pp->pw_name, |
(void)printf("Old key: [%s] %s\n", skey_get_algorithm(), |
ht ? ht : skey_get_algorithm()); |
skey.seed); |
(void)printf("Old seed: [%s] %s\n", |
|
skey_get_algorithm(), skey.seed); |
|
|
/* |
/* |
* Sanity check old seed. |
* Sanity check old seed. |
|
|
} |
} |
} |
} |
|
|
/* |
/* If the seed ends in 0-8 just add one. */ |
* Let's be nice if they have an skey.seed that |
|
* ends in 0-8 just add one |
|
*/ |
|
if (l > 0) { |
if (l > 0) { |
lastc = skey.seed[l - 1]; |
lastc = skey.seed[l - 1]; |
if (isdigit(lastc) && lastc != '9') { |
if (isdigit(lastc) && lastc != '9') { |
|
|
case 1: |
case 1: |
if (zerokey) |
if (zerokey) |
errx(1, "You have no entry to zero."); |
errx(1, "You have no entry to zero."); |
(void)printf("[Adding %s]\n", pp->pw_name); |
(void)printf("[Adding %s with %s]\n", pp->pw_name, |
|
ht ? ht : skey_get_algorithm()); |
|
lockeof(&skey, pp->pw_name); |
break; |
break; |
} |
} |
if (n == 0) |
if (n == 0) |
n = 99; |
n = 99; |
|
|
|
/* Do we have an old-style md4 entry? */ |
|
if (rval == 0 && strcmp("md4", skey_get_algorithm()) == 0 && |
|
strcmp("md4", skey.logname + strlen(skey.logname) + 1) != 0) |
|
oldmd4 = 1; |
|
|
/* Set hash type if asked to */ |
/* Set hash type if asked to */ |
if (ht) { |
if (ht && strcmp(ht, skey_get_algorithm()) != 0) |
/* Need to zero out old key when changing algorithm */ |
skey_set_algorithm(ht); |
if (strcmp(ht, skey_get_algorithm()) && skey_set_algorithm(ht)) |
|
zerokey = 1; |
|
} |
|
|
|
if (!defaultsetup) { |
alarm(180); |
(void)printf("You need the 6 english words generated from the \"skey\" command.\n"); |
if (!defaultsetup) |
for (i = 0; ; i++) { |
secure_mode(&n, key, seed, defaultseed, buf, sizeof(buf)); |
if (i >= 2) |
else |
exit(1); |
normal_mode(pp->pw_name, n, key, seed, defaultseed); |
|
alarm(0); |
|
|
(void)printf("Enter sequence count from 1 to %d: ", |
(void)time(&now); |
SKEY_MAX_SEQ); |
tm = localtime(&now); |
(void)fgets(buf, sizeof(buf), stdin); |
(void)strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm); |
n = atoi(buf); |
|
if (n > 0 && n < SKEY_MAX_SEQ) |
|
break; /* Valid range */ |
|
(void)printf("Error: Count must be > 0 and < %d\n", |
|
SKEY_MAX_SEQ); |
|
} |
|
|
|
for (i = 0;; i++) { |
/* If this is an exiting entry, compute the line length and seed pad */ |
if (i >= 2) |
seedlen = SKEY_MAX_SEED_LEN; |
exit(1); |
if (rval == 0) { |
|
int nlen; |
|
|
(void)printf("Enter new key [default %s]: ", |
nlen = strlen(pp->pw_name) + 1 + strlen(skey_get_algorithm()) + |
defaultseed); |
1 + 4 + 1 + strlen(seed) + 1 + 16 + 1 + strlen(tbuf) + 1; |
(void)fgets(seed, sizeof(seed), stdin); |
|
rip(seed); |
/* |
if (seed[0] == '\0') |
* If there was no hash type (md4) add one unless we |
(void)strcpy(seed, defaultseed); |
* are short on space. |
for (p = seed; *p; p++) { |
*/ |
if (isalpha(*p)) { |
if (oldmd4) { |
if (isupper(*p)) |
if (nlen > skey.len) |
*p = tolower(*p); |
nlen -= 4; |
} else if (!isdigit(*p)) { |
else |
(void)puts("Error: seed may only contain alpha numeric characters"); |
oldmd4 = 0; |
break; |
|
} |
|
} |
|
if (*p == '\0') |
|
break; /* Valid seed */ |
|
} |
} |
if (strlen(seed) > SKEY_MAX_SEED_LEN) { |
|
(void)printf("Notice: Seed truncated to %d characters.\n", |
/* If new entry is longer than the old, comment out the old. */ |
SKEY_MAX_SEED_LEN); |
if (nlen > skey.len) { |
seed[SKEY_MAX_SEED_LEN] = '\0'; |
(void)skeyzero(&skey, pp->pw_name); |
|
/* Re-open keys file and seek to the end */ |
|
if (skeylookup(&skey, pp->pw_name) == -1) |
|
err(1, "cannot reopen database"); |
|
lockeof(&skey, pp->pw_name); |
|
} else { |
|
/* Compute how much to space-pad the seed */ |
|
seedlen = strlen(seed) + (skey.len - nlen); |
} |
} |
|
} |
|
|
for (i = 0;; i++) { |
if ((skey.val = (char *)malloc(16 + 1)) == NULL) |
if (i >= 2) |
err(1, "Can't allocate memory"); |
exit(1); |
btoa8(skey.val, key); |
|
|
(void)printf("otp-%s %d %s\nS/Key access password: ", |
/* Don't save algorithm type for md4 (maintain record length) */ |
skey_get_algorithm(), n, seed); |
/* XXX - should check return values of fprintf + fclose */ |
(void)fgets(buf, sizeof(buf), stdin); |
if (oldmd4) |
rip(buf); |
(void)fprintf(skey.keyfile, "%s %04d %-* %s %-21s\n", |
backspace(buf); |
pp->pw_name, n, seedlen, seed, skey.val, tbuf); |
|
else |
|
(void)fprintf(skey.keyfile, "%s %s %04d %-*s %s %-21s\n", |
|
pp->pw_name, skey_get_algorithm(), n, seedlen, seed, |
|
skey.val, tbuf); |
|
(void)fclose(skey.keyfile); |
|
|
if (buf[0] == '?') { |
(void)printf("\nID %s skey is otp-%s %d %s\n", pp->pw_name, |
(void)puts("Enter 6 English words from secure S/Key calculation."); |
skey_get_algorithm(), n, seed); |
continue; |
(void)printf("Next login password: %s\n\n", |
} else if (buf[0] == '\0') |
hexmode ? put8(buf, key) : btoe(buf, key)); |
exit(1); |
exit(0); |
if (etob(key, buf) == 1 || atob8(key, buf) == 0) |
} |
break; /* Valid format */ |
|
(void)puts("Invalid format - try again with 6 English words."); |
|
} |
|
} else { |
|
/* Get user's secret password */ |
|
fputs("Reminder - Only use this method if you are directly connected\n or have an encrypted channel. If you are using telnet\n or rlogin, exit with no password and use skeyinit -s.\n", stderr); |
|
|
|
for (i = 0;; i++) { |
void |
if (i > 2) |
lockeof(mp, user) |
exit(1); |
struct skey *mp; |
|
char *user; |
|
{ |
|
struct flock fl; |
|
|
(void)fputs("Enter secret password: ", stderr); |
fseek(mp->keyfile, 0, SEEK_END); |
readpass(passwd, sizeof(passwd)); |
dolock: |
if (passwd[0] == '\0') |
fl.l_start = ftell(mp->keyfile); |
exit(1); |
fl.l_len = mp->len; |
|
fl.l_pid = getpid(); |
|
fl.l_type = F_WRLCK; |
|
fl.l_whence = SEEK_SET; |
|
|
if (strlen(passwd) < SKEY_MIN_PW_LEN) { |
if (fcntl(fileno(mp->keyfile), F_SETLKW, &fl) == -1) |
(void)fprintf(stderr, |
err(1, "Can't lock database"); |
"Your password must be at least %d characters long.\n", SKEY_MIN_PW_LEN); |
|
continue; |
|
} else if (strcmp(passwd, pp->pw_name) == 0) { |
|
(void)fputs("Your password may not be the same as your user name.\n", stderr); |
|
continue; |
|
} else if (strspn(passwd, "abcdefghijklmnopqrstuvwxyz") == strlen(passwd)) { |
|
(void)fputs("Your password must contain more than just lower case letters.\nWhitespace, numbers, and puctuation are suggested.\n", stderr); |
|
continue; |
|
} |
|
|
|
(void)fputs("Again secret password: ", stderr); |
/* Make sure we are still at the end. */ |
readpass(passwd2, sizeof(passwd)); |
fseek(mp->keyfile, 0, SEEK_END); |
|
if (fl.l_start == ftell(mp->keyfile)) |
|
return; /* still at EOF */ |
|
|
if (strcmp(passwd, passwd2) == 0) |
fclose(mp->keyfile); |
break; |
if (skeylookup(mp, user) != 1) |
|
errx(1, "user %s already added", user); |
|
goto dolock; |
|
} |
|
|
(void)fputs("Passwords do not match.\n", stderr); |
void |
} |
secure_mode(count, key, seed, defaultseed, buf, bufsiz) |
|
int *count; |
|
char *key; |
|
char *seed; |
|
char *defaultseed; |
|
char *buf; |
|
size_t bufsiz; |
|
{ |
|
int i, n; |
|
char *p; |
|
|
/* Crunch seed and password into starting key */ |
(void)puts("You need the 6 words generated from the \"skey\" command."); |
(void)strcpy(seed, defaultseed); |
for (i = 0; ; i++) { |
if (keycrunch(key, seed, passwd) != 0) |
if (i >= 2) |
err(2, "key crunch failed"); |
exit(1); |
|
|
nn = n; |
(void)printf("Enter sequence count from 1 to %d: ", |
while (nn-- != 0) |
SKEY_MAX_SEQ); |
f(key); |
(void)fgets(buf, bufsiz, stdin); |
|
clearerr(stdin); |
|
n = atoi(buf); |
|
if (n > 0 && n < SKEY_MAX_SEQ) |
|
break; /* Valid range */ |
|
(void)fprintf(stderr, "ERROR: Count must be between 1 and %d\n", |
|
SKEY_MAX_SEQ); |
} |
} |
(void)time(&now); |
|
tm = localtime(&now); |
|
(void)strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm); |
|
|
|
if ((skey.val = (char *)malloc(16 + 1)) == NULL) |
for (i = 0; ; i++) { |
err(1, "Can't allocate memory"); |
if (i >= 2) |
|
exit(1); |
|
|
/* Zero out old key if necesary (entry would change size) */ |
(void)printf("Enter new seed [default %s]: ", |
if (zerokey) { |
defaultseed); |
(void)skeyzero(&skey, pp->pw_name); |
(void)fgets(seed, SKEY_MAX_SEED_LEN+2, stdin); /* XXX */ |
/* Re-open keys file and seek to the end */ |
clearerr(stdin); |
if (skeylookup(&skey, pp->pw_name) == -1) |
rip(seed); |
err(1, "cannot open database"); |
if (strlen(seed) > SKEY_MAX_SEED_LEN) { |
|
(void)fprintf(stderr, "ERROR: Seed must be between 1 " |
|
"and %d characters in length\n", SKEY_MAX_SEED_LEN); |
|
continue; |
|
} |
|
if (seed[0] == '\0') |
|
(void)strcpy(seed, defaultseed); |
|
for (p = seed; *p; p++) { |
|
if (isspace(*p)) { |
|
(void)fputs("ERROR: Seed must not contain " |
|
"any spaces\n", stderr); |
|
break; |
|
} else if (isalpha(*p)) { |
|
if (isupper(*p)) |
|
*p = tolower(*p); |
|
} else if (!isdigit(*p)) { |
|
(void)fputs("ERROR: Seed must be purely " |
|
"alphanumeric\n", stderr); |
|
break; |
|
} |
|
} |
|
if (*p == '\0') |
|
break; /* Valid seed */ |
} |
} |
|
|
btoa8(skey.val, key); |
for (i = 0; ; i++) { |
|
if (i >= 2) |
|
exit(1); |
|
|
/* |
(void)printf("otp-%s %d %s\nS/Key access password: ", |
* Obtain an exclusive lock on the key file so we don't |
skey_get_algorithm(), n, seed); |
* clobber someone authenticating themselves at the same time. |
(void)fgets(buf, bufsiz, stdin); |
*/ |
clearerr(stdin); |
for (i = 0; i < 300; i++) { |
rip(buf); |
if ((rval = flock(fileno(skey.keyfile), LOCK_EX|LOCK_NB)) == 0 |
backspace(buf); |
|| errno != EWOULDBLOCK) |
|
|
if (buf[0] == '?') { |
|
(void)puts("Enter 6 words from secure S/Key calculation."); |
|
continue; |
|
} else if (buf[0] == '\0') |
|
exit(1); |
|
|
|
if (etob(key, buf) == 1 || atob8(key, buf) == 0) |
|
break; /* Valid format */ |
|
(void)fputs("ERROR: Invalid format - try again with the 6 words.\n", |
|
stderr); |
|
} |
|
*count= n; |
|
} |
|
|
|
void |
|
normal_mode(username, n, key, seed, defaultseed) |
|
char *username; |
|
int n; |
|
char *key; |
|
char *seed; |
|
char *defaultseed; |
|
{ |
|
int i, nn; |
|
char passwd[SKEY_MAX_PW_LEN+2], passwd2[SKEY_MAX_PW_LEN+2]; |
|
|
|
/* Get user's secret passphrase */ |
|
for (i = 0; ; i++) { |
|
if (i > 2) |
|
exit(1); |
|
|
|
if (readpassphrase("Enter secret passphrase: ", passwd, |
|
sizeof(passwd), 0) == NULL || passwd[0] == '\0') |
|
exit(1); |
|
|
|
if (strlen(passwd) < SKEY_MIN_PW_LEN) { |
|
(void)fprintf(stderr, |
|
"ERROR: Your passphrase must be at least %d " |
|
"characters long.\n", SKEY_MIN_PW_LEN); |
|
continue; |
|
} else if (strcmp(passwd, username) == 0) { |
|
(void)fputs("ERROR: Your passphrase may not be the " |
|
"same as your user name.\n", stderr); |
|
continue; |
|
} else if (strspn(passwd, "abcdefghijklmnopqrstuvwxyz") == |
|
strlen(passwd)) { |
|
(void)fputs("ERROR: Your passphrase must contain more " |
|
"than just lower case letters.\nWhitespace, " |
|
"numbers, and puctuation are suggested.\n", stderr); |
|
continue; |
|
} else if (strlen(passwd) > 63) { |
|
(void)fprintf(stderr, "WARNING: Your passphrase is " |
|
"longer than the recommended maximum length of 63\n"); |
|
} |
|
/* XXX - should check for passphrase that is really too long */ |
|
|
|
if (readpassphrase("Again secret passphrase: ", passwd2, |
|
sizeof(passwd2), 0) && strcmp(passwd, passwd2) == 0) |
break; |
break; |
usleep(100000); /* Sleep for 0.1 seconds */ |
|
|
(void)fputs("Passphrases do not match.\n", stderr); |
} |
} |
if (rval == -1) { /* Can't get exclusive lock */ |
|
errno = EAGAIN; |
|
err(1, "cannot open database"); |
|
} |
|
|
|
/* Don't save algorithm type for md4 (keep record length same) */ |
/* Crunch seed and passphrase into starting key */ |
if (strcmp(skey_get_algorithm(), "md4") == 0) |
(void)strcpy(seed, defaultseed); |
(void)fprintf(skey.keyfile, "%s %04d %-16s %s %-21s\n", |
if (keycrunch(key, seed, passwd) != 0) |
pp->pw_name, n, seed, skey.val, tbuf); |
err(2, "key crunch failed"); |
else |
|
(void)fprintf(skey.keyfile, "%s %s %04d %-16s %s %-21s\n", |
|
pp->pw_name, skey_get_algorithm(), n, seed, skey.val, tbuf); |
|
|
|
(void)fclose(skey.keyfile); |
nn = n; |
|
while (nn-- != 0) |
|
f(key); |
|
} |
|
|
(void)printf("\nID %s skey is otp-%s %d %s\n", pp->pw_name, |
#define TIMEOUT_MSG "Timed out waiting for input.\n" |
skey_get_algorithm(), n, seed); |
void |
(void)printf("Next login password: %s\n\n", |
timedout(signo) |
hexmode ? put8(buf, key) : btoe(buf, key)); |
int signo; |
exit(0); |
{ |
|
|
|
write(STDERR_FILENO, TIMEOUT_MSG, sizeof(TIMEOUT_MSG) - 1); |
|
_exit(1); |
} |
} |
|
|
void |
void |