otpw/0042755000307500030750000000000007740300205010512 5ustar mgk25mgk25otpw/conf.h0100644000307500030750000000334207740300202011602 0ustar mgk25mgk25/* * One-time password login capability - configuration options * * Markus Kuhn * * $Id: conf.h,v 1.5 2003-08-31 20:25:45+01 mgk25 Rel $ */ #ifndef OTPW_CONF_H #define OTPW_CONF_H /* * List of shell commands that produce high entropy output. * The output of all these commands will be hashed together with * timing information to seed the random number generator */ #define ENTROPY_CMDS \ "head -c 20 /dev/urandom 2>&1", \ "ls -lu /etc/. /tmp/. / /usr/. /bin/. /usr/bin/.", \ "PATH=/usr/ucb:/bin:/usr/bin;ps lax", \ "last | head -50", \ "uptime;netstat -n;hostname;date;w", \ "cd $HOME; cat .pgp/randseed.bin .ssh/random_seed .otpw 2>&1" /* too slow: "PATH=/usr/bin/X11/;xwd -root -silent 2>&1||xwd -root 2>&1" */ /* * Environment variable settings for the entropy generating * shell commands */ #define ENTROPY_ENV \ "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/etc:/usr/etc:/usr/ucb" /* * Path for the one-time password file. Pathnames not starting with * a slash will be relative to the user's home directory. */ #define OTPW_FILE ".otpw" /* * Path for the temporary version of OTPW_FILE (needed for atomicity) */ #define OTPW_TMP ".otpw.tmp" /* * Path for the one-time password lock symlink. */ #define OTPW_LOCK ".otpw.lock" /* * Number of concatenated passwords requested while another one is locked. * A reasonable value is 3. */ #define OTPW_MULTI 3 /* * Stored hash is OTPW_HLEN characters or OTPW_HLEN*6 bits long. * A reasonable value is 3 (72 bits). */ #define OTPW_HLEN 12 /* * Characteristic first line that allows recognicion of an OTPW file */ #define OTPW_MAGIC "OTPW1\n" /* * Minimum password entropy [bits] permitted by otpw-gen (option -e) */ #define EMIN 30 #endif otpw/demologin.c0100644000307500030750000000716307740300202012632 0ustar mgk25mgk25/* * Simple demonstration application that supports one-time passwords * * Markus Kuhn * * $Id: demologin.c,v 1.7 2003-08-31 21:51:34+01 mgk25 Rel $ */ #define _XOPEN_SOURCE /* to get crypt() from */ #define SHADOW_PW /* define if the shadow password system is used */ #include #include #include #include #include #include #include #include #include #include #include #ifdef SHADOW_PW #include #endif #include "otpw.h" int main(int argc, char **argv) { char username[81] = "", password[81]; struct termios term, term_old; int stdin_is_tty = 0, use_otpw, result; struct passwd *pwd; struct challenge ch; int i, debug = 0; #ifdef SHADOW_PW struct spwd* spwd; #endif for (i = 1; i < argc; i++) { if (argv[i][0] == '-') switch (argv[i][1]) { case 'd': debug = 1; break; default: fprintf(stderr, "usage: %s [-d] [username][/]\n", argv[0]); exit(1); } else { /* get user name from command line */ strncpy(username, argv[i], sizeof(username)); username[sizeof(username) - 1] = 0; } } if (!*username) { printf("Append a slash (/) to your user name to activate OTPW.\n\n"); /* ask for the user name */ printf("login: "); fgets(username, sizeof(username), stdin); /* remove '\n' */ username[strlen(username) - 1] = 0; } /* check if one-time password mode was requested by appending slash */ use_otpw = username[strlen(username) - 1] == '/'; /* if yes, remove slash from entered username */ if (use_otpw) username[strlen(username) - 1] = 0; /* read the user database entry */ pwd = getpwnam(username); /* in one-time password mode, set lock and output challenge string */ if (use_otpw) { otpw_prepare(&ch, pwd, debug ? OTPW_DEBUG : 0); if (!ch.challenge[0]) { printf("Sorry, one-time password entry not possible at the moment.\n"); exit(1); } /* ask for the password */ printf("Password %s: ", ch.challenge); } else printf("Password: "); /* disable echo if stdin is a terminal */ if (tcgetattr(fileno(stdin), &term)) { if (errno != ENOTTY) { perror("tcgetattr"); if (use_otpw) otpw_verify(&ch, password); exit(2); } } else { stdin_is_tty = 1; term_old = term; term.c_lflag &= ~(ECHO | ECHOE | ECHOK); term.c_lflag |= ECHONL; if (tcsetattr(fileno(stdin), TCSAFLUSH, &term)) { perror("tcsetattr"); return 1; } } /* read the password */ fgets(password, sizeof(password), stdin); /* remove '\n' */ password[strlen(password) - 1] = 0; /* reenable echo */ if (stdin_is_tty) tcsetattr(fileno(stdin), TCSANOW, &term_old); /* check password */ if (use_otpw) { /* one-time password check */ result = otpw_verify(&ch, password); if (result == OTPW_OK) { printf("Login correct\n"); if (ch.entries > 2 * ch.remaining) printf("Only %d one-time passwords left (%d%%), please generate " "new list.\n", ch.remaining, ch.remaining * 100 / ch.entries); } else printf("Login incorrect\n"); } else { /* old-style many-time password check */ #ifdef SHADOW_PW spwd = getspnam(username); if (pwd && spwd) pwd->pw_passwd = spwd->sp_pwdp; if (geteuid() != 0) fprintf(stderr, "Shadow password access requires root priviliges.\n"); #endif if (!pwd || strcmp(crypt(password, pwd->pw_passwd), pwd->pw_passwd)) printf("Login incorrect\n"); else printf("Login correct\n"); } return 0; } otpw/md.c0100644000307500030750000001253707740300202011256 0ustar mgk25mgk25/* * Universal wrapper API for a message digest function * * Markus Kuhn * * $Id: md.c,v 1.4 2003-06-19 18:37:11+01 mgk25 Rel $ */ #include #include #include #include #include "md.h" #include "rmd160.h" void md_init(md_state * md) { /* check assumptions made in rmd160.h (should produce no code) */ assert(sizeof(dword) == 4); assert(sizeof(word) == 2); assert(sizeof(byte) == 1); md->length_lo = md->length_hi = 0; rmd160_init((dword *) md->md); } void md_add(md_state *md, unsigned char *src, unsigned long len) { int i; int remaining = md->length_lo & (MD_BUFLEN-1); unsigned chunk; unsigned long old_lo = md->length_lo; dword X[16]; md->length_lo += len & 0xffffffff; if (md->length_lo < old_lo) md->length_hi++; #if ULONG_MAX > 4294967295U md->length_hi += len >> 32; if (md->length_lo > 4294967295U) { md->length_hi += md->length_lo >> 32; md->length_lo &= 0xffffffff; } #endif if (remaining > 0) { chunk = MD_BUFLEN - remaining; if (chunk > len) chunk = len; memcpy(md->buf + remaining, src, chunk); len -= chunk; src += chunk; if (remaining + chunk == MD_BUFLEN) { for (i = 0; i < 64; i += 4) X[i>>2] = (dword) md->buf[i] | ((dword) md->buf[i+1] << 8) | ((dword) md->buf[i+2] << 16) | ((dword) md->buf[i+3] << 24); rmd160_compress((dword *) md->md, X); } } while (len >= MD_BUFLEN) { for (i = 0; i < 64; i += 4) X[i>>2] = (dword) src[i] | ((dword) src[i+1] << 8) | ((dword) src[i+2] << 16) | ((dword) src[i+3] << 24); rmd160_compress((dword *) md->md, X); src += MD_BUFLEN; len -= MD_BUFLEN; } if (len > 0) memcpy(md->buf, src, len); } void md_close(md_state * md, unsigned char *result) { int i; rmd160_finish((dword *) md->md, (byte *) md->buf, (dword) md->length_lo, (dword) md->length_hi); for (i = 0; i < MD_LEN; i++) result[i] = ((dword *) md->md)[i>>2] >> (8 * (i & 3)); } int md_selftest(void) { int i, j, fail = 0; md_state md; unsigned char result[MD_LEN]; char *pattern[8] = { "", "a", "abc", "message digest", "abcdefghijklmnopqrstuvwxyz", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "123456789012345678901234567890123456789012345678901234567890" "12345678901234567890" }; #ifdef MD_RIPEMD128 unsigned char md_result[9][MD_LEN] = { { 0xcd, 0xf2, 0x62, 0x13, 0xa1, 0x50, 0xdc, 0x3e, 0xcb, 0x61, 0x0f, 0x18, 0xf6, 0xb3, 0x8b, 0x46 }, { 0x86, 0xbe, 0x7a, 0xfa, 0x33, 0x9d, 0x0f, 0xc7, 0xcf, 0xc7, 0x85, 0xe7, 0x2f, 0x57, 0x8d, 0x33 }, { 0xc1, 0x4a, 0x12, 0x19, 0x9c, 0x66, 0xe4, 0xba, 0x84, 0x63, 0x6b, 0x0f, 0x69, 0x14, 0x4c, 0x77 }, { 0x9e, 0x32, 0x7b, 0x3d, 0x6e, 0x52, 0x30, 0x62, 0xaf, 0xc1, 0x13, 0x2d, 0x7d, 0xf9, 0xd1, 0xb8 }, { 0xfd, 0x2a, 0xa6, 0x07, 0xf7, 0x1d, 0xc8, 0xf5, 0x10, 0x71, 0x49, 0x22, 0xb3, 0x71, 0x83, 0x4e }, { 0xa1, 0xaa, 0x06, 0x89, 0xd0, 0xfa, 0xfa, 0x2d, 0xdc, 0x22, 0xe8, 0x8b, 0x49, 0x13, 0x3a, 0x06 }, { 0xd1, 0xe9, 0x59, 0xeb, 0x17, 0x9c, 0x91, 0x1f, 0xae, 0xa4, 0x62, 0x4c, 0x60, 0xc5, 0xc7, 0x02 }, { 0x3f, 0x45, 0xef, 0x19, 0x47, 0x32, 0xc2, 0xdb, 0xb2, 0xc4, 0xa2, 0xc7, 0x69, 0x79, 0x5f, 0xa3 }, { 0x4a, 0x7f, 0x57, 0x23, 0xf9, 0x54, 0xeb, 0xa1, 0x21, 0x6c, 0x9d, 0x8f, 0x63, 0x20, 0x43, 0x1f } }; #endif #ifdef MD_RIPEMD160 unsigned char md_result[9][MD_LEN] = { { 0x9c, 0x11, 0x85, 0xa5, 0xc5, 0xe9, 0xfc, 0x54, 0x61, 0x28, 0x08, 0x97, 0x7e, 0xe8, 0xf5, 0x48, 0xb2, 0x25, 0x8d, 0x31 }, { 0x0b, 0xdc, 0x9d, 0x2d, 0x25, 0x6b, 0x3e, 0xe9, 0xda, 0xae, 0x34, 0x7b, 0xe6, 0xf4, 0xdc, 0x83, 0x5a, 0x46, 0x7f, 0xfe }, { 0x8e, 0xb2, 0x08, 0xf7, 0xe0, 0x5d, 0x98, 0x7a, 0x9b, 0x04, 0x4a, 0x8e, 0x98, 0xc6, 0xb0, 0x87, 0xf1, 0x5a, 0x0b, 0xfc }, { 0x5d, 0x06, 0x89, 0xef, 0x49, 0xd2, 0xfa, 0xe5, 0x72, 0xb8, 0x81, 0xb1, 0x23, 0xa8, 0x5f, 0xfa, 0x21, 0x59, 0x5f, 0x36 }, { 0xf7, 0x1c, 0x27, 0x10, 0x9c, 0x69, 0x2c, 0x1b, 0x56, 0xbb, 0xdc, 0xeb, 0x5b, 0x9d, 0x28, 0x65, 0xb3, 0x70, 0x8d, 0xbc }, { 0x12, 0xa0, 0x53, 0x38, 0x4a, 0x9c, 0x0c, 0x88, 0xe4, 0x05, 0xa0, 0x6c, 0x27, 0xdc, 0xf4, 0x9a, 0xda, 0x62, 0xeb, 0x2b }, { 0xb0, 0xe2, 0x0b, 0x6e, 0x31, 0x16, 0x64, 0x02, 0x86, 0xed, 0x3a, 0x87, 0xa5, 0x71, 0x30, 0x79, 0xb2, 0x1f, 0x51, 0x89 }, { 0x9b, 0x75, 0x2e, 0x45, 0x57, 0x3d, 0x4b, 0x39, 0xf4, 0xdb, 0xd3, 0x32, 0x3c, 0xab, 0x82, 0xbf, 0x63, 0x32, 0x6b, 0xfb }, { 0x52, 0x78, 0x32, 0x43, 0xc1, 0x69, 0x7b, 0xdb, 0xe1, 0x6d, 0x37, 0xf9, 0x7f, 0x68, 0xf0, 0x83, 0x25, 0xdc, 0x15, 0x28 } }; #endif for (i = 0; i <= 16; i++) { md_init(&md); if (i == 16) /* test 16: one million 'a' */ for (j = 0; j < 1000000; j += 125) md_add(&md, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 125); else if (i & 1) /* single byte feed */ for (j = 0; pattern[i/2][j]; j++) md_add(&md, pattern[i/2]+j, 1); else /* single chunk feed */ md_add(&md, pattern[i/2], strlen(pattern[i/2])); md_close(&md, result); if (memcmp(result, md_result[i/2], MD_LEN) != 0) { abort(); fail++; } } return fail; } otpw/md.h0100644000307500030750000000135307740300202011255 0ustar mgk25mgk25/* * Universal wrapper API for a message digest function * * Markus Kuhn * * $Id: md.h,v 1.3 2003-06-19 18:37:11+01 mgk25 Rel $ */ #ifndef MD_H #define MD_H #define MD_RIPEMD160 #ifdef MD_RIPEMD160 #define MD_LEN 20 #define MD_BUFLEN 64 #endif typedef struct { unsigned char md[MD_LEN]; /* internal status of hash function */ unsigned char buf[MD_BUFLEN]; /* buffer for stream-like interface */ unsigned long length_lo, length_hi; /* number of bits hashed so far */ } md_state; /* prototypes */ void md_init(md_state * md); void md_add(md_state * md, unsigned char *src, unsigned long len); void md_close(md_state * md, unsigned char *result); int md_selftest(void); #endif otpw/otpw.c0100644000307500030750000003255507740300202011651 0ustar mgk25mgk25/* * One-time password login capability * * Markus Kuhn * */ static char const rcsid[] = "$Id: otpw.c,v 1.9 2003-09-30 21:11:32+01 mgk25 Rel $"; #include #include #include #include #include #include #include #include #include #include #include #include #include "otpw.h" #include "conf.h" #include "md.h" #ifndef DEBUG_LOG #define DEBUG_LOG(...) if (ch->flags & OTPW_DEBUG) \ { fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); } #endif /* * A random bit generator. Hashes together some quick sources of entropy * to provide some reasonable random seed. (High entropy is not security * critical here.) */ void rbg_seed(unsigned char *r) { int devrandom; char rbs[MD_LEN]; md_state md; struct { clock_t clk; pid_t pid; uid_t uid; pid_t ppid; struct timeval t; } entropy; md_init(&md); /* read out kernel random number generator device if there is one */ devrandom = open("/dev/urandom", O_RDONLY); if (devrandom >= 0) { read(devrandom, rbs, sizeof(rbs)); md_add(&md, rbs, sizeof(rbs)); close(devrandom); } /* other minor sources of entropy */ entropy.clk = clock(); entropy.uid = getuid(); entropy.pid = getpid(); entropy.ppid = getppid(); gettimeofday(&entropy.t, NULL); md_add(&md, (unsigned char *) &entropy, sizeof(entropy)); md_close(&md, r); } void rbg_iter(unsigned char *r) { md_state md; struct timeval t; md_init(&md); gettimeofday(&t, NULL); md_add(&md, (unsigned char *) &t, sizeof(t)); md_add(&md, r, MD_LEN); md_add(&md, "AutomaGic", 9); /* feel free to change this as a site key */ md_close(&md, r); } /* * Transform the first 6*chars bits of the binary string v into a chars * character long string s. The encoding is a modification of the MIME * base64 encoding where characters with easily confused glyphs are * avoided (0 vs O, 1 vs. l vs. I). */ void conv_base64(char *s, const unsigned char *v, int chars) { const char tab[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk%mnopqrstuvwxyz" ":=23456789+/"; int i, j; for (i = 0; i < chars; i++) { j = (i / 4) * 3; switch (i % 4) { case 0: *s++ = tab[ v[j] >>2]; break; case 1: *s++ = tab[((v[j] <<4) & 0x30) | (v[j+1]>>4)]; break; case 2: *s++ = tab[((v[j+1]<<2) & 0x3c) | (v[j+2]>>6)]; break; case 3: *s++ = tab[ v[j+2] & 0x3f]; break; } } *s++ = '\0'; } void otpw_prepare(struct challenge *ch, struct passwd *user, int flags) { FILE *f = NULL; int i, j; int count, repeat; int olduid = -1; int oldgid = -1; char line[81]; char lock[81]; unsigned char r[MD_LEN]; struct stat lbuf; char *hbuf = NULL; /* list of challenges and hashed passwords */ int challen, hlen, hbuflen; if (!ch) { DEBUG_LOG("!ch"); return; } ch->passwords = 0; ch->remaining = -1; ch->entries = -1; ch->pwlen = 0; ch->locked = 0; ch->challenge[0] = 0; ch->flags = flags; if (!user) { DEBUG_LOG("No password database entry provided!"); return; } ch->uid = user->pw_uid; ch->gid = user->pw_gid; /* set effective uid/gui temporarily */ olduid = geteuid(); oldgid = getegid(); if (setegid(ch->gid)) DEBUG_LOG("Failed to change egid %d -> %d", oldgid, ch->gid); if (seteuid(ch->uid)) DEBUG_LOG("Failed to change euid %d -> %d", olduid, ch->uid); /* open password file */ if (chdir(user->pw_dir)) { DEBUG_LOG("chdir(\"%s\"): %s", user->pw_dir, strerror(errno)); goto cleanup; } if (!(f = fopen(OTPW_FILE, "r"))) { DEBUG_LOG("fopen(\"%s\", \"r\"): %s", OTPW_FILE, strerror(errno)); goto cleanup; } /* prepare random number generator */ rbg_seed(r); /* check header */ if (!fgets(line, sizeof(line), f) || strcmp(line, OTPW_MAGIC) || !fgets(line, sizeof(line), f) || ((line[0] == '#') && !fgets(line, sizeof(line), f)) || sscanf(line, "%d%d%d%d\n", &ch->entries, &challen, &hlen, &ch->pwlen) != 4) { DEBUG_LOG("Header in '" OTPW_FILE "' wrong!"); goto cleanup; } if (ch->entries < 1 || ch->entries > 9999 || challen < 1 || (challen + 1) * OTPW_MULTI > (int)sizeof(ch->challenge) || ch->pwlen < 4 || ch->pwlen > 999 || hlen != OTPW_HLEN) { DEBUG_LOG("Header parameters (%d %d %d %d) out of allowed range!", ch->entries, challen, hlen, ch->pwlen); goto cleanup; } hbuflen = challen + hlen; hbuf = malloc(ch->entries * hbuflen); if (!hbuf) { DEBUG_LOG("malloc failed"); return; } ch->remaining = 0; j = -1; for (i = 0; i < ch->entries; i++) { if (!fgets(line, sizeof(line), f) || (int) strlen(line) != hbuflen + 1) { DEBUG_LOG("'" OTPW_FILE "' too short!"); goto cleanup; } memcpy(hbuf + i*hbuflen, line, hbuflen); if (hbuf[i*hbuflen] != '-') { ch->remaining++; if (j < 0) j = i; /* select first unused hash */ } } if (ch->remaining < 1) { DEBUG_LOG("No passwords left!"); goto cleanup; } strncpy(ch->challenge, hbuf + j*hbuflen, challen); ch->challenge[challen] = 0; ch->selection[0] = j; strncpy(ch->hash[0], hbuf + j*hbuflen + challen, OTPW_HLEN); ch->hash[0][OTPW_HLEN] = 0; if (ch->flags & OTPW_NOLOCK) { /* we were told not to worry about locking */ ch->passwords = 1; goto cleanup; } count = 0; do { repeat = 0; /* try to get a lock on this one */ if (symlink(ch->challenge, OTPW_LOCK) == 0) { /* ok, we got the lock */ ch->passwords = 1; ch->locked = 1; goto cleanup; } if (errno != EEXIST) { DEBUG_LOG("symlink(\"%s\", \"" OTPW_LOCK "\"): %s", ch->challenge, strerror(errno)); ch->challenge[0] = 0; goto cleanup; } if (lstat(OTPW_LOCK, &lbuf) == 0) { if (time(NULL) - lbuf.st_mtime > 60 * 60 * 24) { /* remove a stale lock after 24h, login should have timed out */ unlink(OTPW_LOCK); repeat = 1; } } else if (errno == ENOENT) repeat = 1; else { DEBUG_LOG("lstat(\"" OTPW_LOCK "\", ...): %s", strerror(errno)); ch->challenge[0] = 0; goto cleanup; } } while (repeat && ++count < 5); ch->challenge[0] = 0; /* ok, there is already a fresh lock, so someone is currently logging in */ i = readlink(OTPW_LOCK, lock, sizeof(lock)-1); if (i > 0) { lock[i] = 0; if ((int) strlen(lock) != challen) { /* lock symlink seems to have been corrupted */ DEBUG_LOG("Removing corrupt lock symlink."); unlink(OTPW_LOCK); } } else if (errno != ENOENT) { DEBUG_LOG("Could not read lock symlink."); goto cleanup; } /* now we generate OTPW_MULTI challenges */ if (ch->remaining < OTPW_MULTI+1 || ch->remaining < 10) { DEBUG_LOG("%d remaining passwords are not enough for " "multi challenge.", ch->remaining); goto cleanup; } while (ch->passwords < OTPW_MULTI && strlen(ch->challenge) < sizeof(ch->challenge) - challen - 2) { count = 0; /* random scan for remaining password */ do { /* pick a random entry */ rbg_iter(r); j = *((unsigned int *) r) % ch->entries; } while ((hbuf[j*hbuflen] == '-' || !strncmp(hbuf + j*hbuflen + challen, lock, challen)) && count++ < 2 * ch->entries); /* fallback scan for remaining password */ while (hbuf[j*hbuflen] == '-' || !strncmp(hbuf + j*hbuflen + challen, lock, challen)) j = (j + 1) % ch->entries; /* add password j to multi challenge */ sprintf(ch->challenge + strlen(ch->challenge), "%s%.*s", ch->passwords ? "/" : "", challen, hbuf + j*hbuflen); strncpy(ch->hash[ch->passwords], hbuf + j*hbuflen + challen, hlen); ch->hash[ch->passwords][hlen] = 0; ch->selection[ch->passwords++] = j; hbuf[j*hbuflen] = '-'; /* avoid same pw occuring twice per challenge */ } cleanup: if (f) fclose(f); /* restore uid/gid */ if (olduid != -1) if (seteuid(olduid)) DEBUG_LOG("Failed when trying to change euid back to %d", olduid); if (oldgid != -1) if (setegid(oldgid)) DEBUG_LOG("Failed when trying to change egid back to %d", oldgid); if (hbuf) free(hbuf); return; } int otpw_verify(struct challenge *ch, char *password) { FILE *f = NULL; int result = OTPW_ERROR; int i, j = 0, l; int entries; int deleted, clear; int olduid = -1; int oldgid = -1; char *otpw = NULL; char line[81]; unsigned char h[MD_LEN]; md_state md; int challen, pwlen, hlen; if (!ch) { DEBUG_LOG("!ch"); return OTPW_ERROR; } if (!password || ch->passwords < 1 || ch->passwords > OTPW_MULTI) { DEBUG_LOG("otpw_verify(): Invalid parameters or no challenge issued."); return OTPW_ERROR; } otpw = calloc(ch->passwords, ch->pwlen); if (!otpw) { DEBUG_LOG("malloc failed"); return OTPW_ERROR; } /* set effective uid/gid temporarily */ olduid = geteuid(); oldgid = getegid(); if (setegid(ch->gid)) DEBUG_LOG("Failed when trying to change egid %d -> %d", oldgid, ch->gid); if (seteuid(ch->uid)) DEBUG_LOG("Failed when trying to change euid %d -> %d", olduid, ch->uid); /* * Scan in the one-time passwords, eliminating any spurious characters * (such as whitespace, control characters) that might have been added * accidentally */ l = strlen(password) - 1; for (i = ch->passwords-1; i >= 0 && l >= 0; i--) { for (j = ch->pwlen - 1; j >= 0 && l >= 0; j--) { while (!otpw[i*ch->pwlen + j] && l >= 0) { /* remove DEL/BS characters */ deleted = 0; while (l >= 0 && (password[l] == 8 || password[l] == 127 || deleted > 0)) { if (password[l] == 8 || password[l] == 127) deleted++; else deleted--; l--; } if (l < 0) break; if (password[l] == 'l' || password[l] == '1' || password[l] == '|') otpw[i*ch->pwlen + j] = 'I'; else if (password[l] == '0') otpw[i*ch->pwlen + j] = 'O'; else if (password[l] == '\\') otpw[i*ch->pwlen + j] = '/'; else if ((password[l] >= 'A' && password[l] <= 'Z') || (password[l] >= 'a' && password[l] <= 'z') || (password[l] >= '2' && password[l] <= '9') || password[l] == ':' || password[l] == '%' || password[l] == '=' || password[l] == '+' || password[l] == '/') otpw[i*ch->pwlen + j] = password[l]; l--; } } DEBUG_LOG("Password %d = '%.*s'", i, ch->pwlen, otpw + i*ch->pwlen); } if (i >= 0 || j >= 0) { DEBUG_LOG("Entered password was too short."); result = OTPW_WRONG; goto cleanup; } l++; /* l is now the length of the prefix password */ DEBUG_LOG("Prefix = '%.*s'", l, password); /* now compare all entered passwords */ for (i = 0; i < ch->passwords; i++) { md_init(&md); /* feed prefix password into hash function */ md_add(&md, password, l); /* feed one-time password into hash function */ md_add(&md, otpw + i*ch->pwlen, ch->pwlen); /* transform hash result into the base64 form used in OTPW_FILE */ md_close(&md, h); conv_base64(line, h, OTPW_HLEN); DEBUG_LOG("hash(password): '%s', hash from file: '%s'", line, ch->hash[i]); if (strcmp(line, ch->hash[i])) { DEBUG_LOG("Entered password did not match."); result = OTPW_WRONG; goto cleanup; } } /* ok, all passwords were correct */ result = OTPW_OK; DEBUG_LOG("Entered password(s) are ok."); /* Now overwrite the used passwords in OTPW_FILE */ if (!(f = fopen(OTPW_FILE, "r+"))) { DEBUG_LOG("Failed getting write access to '" OTPW_FILE "': %s", strerror(errno)); goto writefail; } /* check header */ if (!fgets(line, sizeof(line), f) || strcmp(line, OTPW_MAGIC) || !fgets(line, sizeof(line), f) || ((line[0] == '#') && !fgets(line, sizeof(line), f)) || sscanf(line, "%d%d%d%d\n", &entries, &challen, &hlen, &pwlen) != 4 || entries != ch->entries || pwlen != ch->pwlen || hlen != OTPW_HLEN || challen < 1 || (challen + 1) * OTPW_MULTI > (int) sizeof(ch->challenge)) { DEBUG_LOG("Overwrite failed because of header mismatch."); goto writefail; } for (i = 0; i < entries; i++) { clear = 0; for (j = 0; j < ch->passwords; j++) if (ch->selection[j] == i) clear = 1; if (clear) { fseek(f, 0L, SEEK_CUR); for (l = 0; l < challen + hlen; l++) fputc('-', f); fputc('\n', f); fseek(f, 0L, SEEK_CUR); ch->remaining--; } else if (!fgets(line, sizeof(line), f)) { DEBUG_LOG("Overwrite failed because of unexpected EOF."); goto writefail; } } goto cleanup; writefail: /* entered one-time passwords were correct, but overwriting them failed */ if (ch->passwords == 1) { /* for a single password, permit login, but keep lock in place */ DEBUG_LOG("Keeping lock on password."); ch->locked = 0; /* supress removal of lock */ } cleanup: if (f) fclose(f); /* remove lock */ if (ch->locked) { DEBUG_LOG("Removing lock file"); if (unlink(OTPW_LOCK)) DEBUG_LOG("Failed when trying to unlink lock file: %s", strerror(errno)); } /* restore uid/gid */ if (olduid != -1) if (seteuid(olduid)) DEBUG_LOG("Failed when trying to change euid back to %d", olduid); if (oldgid != -1) if (setegid(oldgid)) DEBUG_LOG("Failed when trying to change egid back to %d", oldgid); /* make sure, we are not called a second time */ ch->passwords = 0; if (otpw) free(otpw); return result; } otpw/otpw-gen.c0100644000307500030750000007573407740300202012426 0ustar mgk25mgk25/* * One-time password generator * * Markus Kuhn * */ static char const rcsid[] = "$Id: otpw-gen.c,v 1.11 2003-09-30 21:11:32+01 mgk25 Rel $"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include "conf.h" #include "md.h" #define NL "\r\n" /* new line sequence in password list output */ #define FF "\f\n" /* form feed sequence in password list output */ #define HEADER_LINES 4 /* lines printed in addition to password lines */ #define MAX_PASSWORDS 1000 /* maximum length of password list */ #define CHALLEN 3 /* number of characters in challenge */ #define HBUFLEN (CHALLEN + OTPW_HLEN + 1) /* * A list of common English four letter words. It has not been checked * particularly well for being free of rude words or trademarks, but * that shouldn't be a problem as users should keep them secret anyway. */ char word[2048][4] = { "abel","able","ably","acer","aces","acet","ache","acid","acne","acre", "acts","adam","adds","aden","afar","aged","ages","aide","aids","aims", "airs","airy","ajar","akin","alan","alas","alec","ales","alex","alix", "ally","alma","alps","also","alto","amen","ames","amid","amis","amos", "amps","anal","andy","anew","ange","angy","anna","anne","ante","anti", "ants","anus","anya","aoun","apes","apex","apse","arab","arch","arcs", "ards","area","aria","arid","arms","army","arts","asda","asia","asks", "atom","atop","audi","aung","aunt","aura","auto","avid","aviv","avon", "away","awry","axed","axes","axis","axle","aziz","baba","babe","baby", "bach","back","bade","bags","bail","bait","bake","baku","bald","bale", "bali","ball","balm","band","bang","bank","bans","bare","bark","barn", "barr","bars","bart","base","bash","bass","bath","bats","bays","bcci", "bdda","bead","beak","beam","bean","bear","beat","beck","bede","beds", "beef","been","beep","beer","bees","begs","bell","belt","bend","benn", "bent","berg","bert","best","beta","beth","bets","bias","bids","biff", "bike","bile","bill","bind","bins","bird","birk","birt","bite","bits", "blah","blew","blip","blob","bloc","blot","blow","blue","blur","boar", "boat","bodo","body","boer","bogs","boil","bold","bolt","bomb","bond", "bone","bonn","bono","bony","book","boom","boon","boot","bore","borg", "born","boro","boss","both","bout","bowe","bowl","bows","boyd","boys", "brad","bran","bras","brat","bray","bred","brew","brim","brom","bros", "brow","buck","buds","buff","bugs","bulb","bulk","bull","bump","bums", "bunk","buns","buoy","burn","burr","burt","bury","bush","bust","busy", "butt","buys","buzz","byre","byte","cabs","cafe","cage","cain","cake", "calf","call","calm","came","camp","cane","cans","cape","caps","capt", "cara","card","care","carl","caro","carp","carr","cars","cart","casa", "case","cash","cask","cast","cats","cave","cdna","cegb","cell","cent", "cert","chad","chan","chap","chas","chat","chef","chen","cher","chew", "chic","chin","chip","chop","chub","chum","cite","city","clad","clan", "claw","clay","cleo","clio","clip","club","clue","cnaa","cnut","coal", "coat","coax","coca","code","cohn","coil","coin","coke","cola","cold", "cole","coli","colt","coma","comb","come","comp","cone","cons","cook", "cool","cope","cops","copy","cord","core","cork","corn","corp","cose", "cost","cosy","cots","coun","coup","cove","cows","cpre","cpsu","cpus", "crab","crag","crap","cray","creb","crew","crim","crop","crow","crux", "cruz","csce","cuba","cube","cubs","cues","cuff","cult","cunt","cups", "curb","curd","cure","curl","curt","cute","cuts","daak","dada","dads", "daft","dahl","dais","dale","daly","dame","damn","damp","dams","dana", "dane","dank","dare","dark","dart","dash","data","date","dave","davy", "dawn","days","daze","dead","deaf","deal","dean","dear","debt","deck", "deed","deep","deer","deft","defy","dell","demo","deng","dent","deny", "dept","desk","dial","dice","dick","died","dies","diet","digs","dine", "ding","dino","dint","dire","dirk","dirt","disc","dish","disk","dive", "dock","dodd","does","dogs","dole","doll","dome","done","dons","doom", "door","dope","dora","dose","doth","dots","doug","dour","dove","dowd", "down","drab","drag","draw","drew","drip","drop","drug","drum","dual", "duck","duct","duel","dues","duet","duff","duke","dull","duly","duma", "dumb","dump","dune","dung","dunn","dusk","dust","duty","dyer","dyes", "dyke","each","earl","earn","ears","ease","east","easy","eats","echo", "ecsc","eddy","eden","edge","edgy","edie","edit","edna","edta","eels", "efta","egan","eggs","egon","egos","eire","ella","else","emil","emit", "emma","ends","enid","envy","epic","ercp","eric","erik","esau","esrc", "esso","eton","euro","evan","even","ever","evil","ewen","ewes","exam", "exit","exon","expo","eyed","eyes","eyre","ezra","face","fact","fade", "fads","fags","fail","fair","fake","fall","fame","fand","fans","fare", "farm","farr","fast","fate","fats","fawn","faye","fear","feat","feed", "feel","fees","feet","fell","felt","fend","fenn","fens","fern","fete", "feud","fiat","fife","figs","fiji","file","fill","film","find","fine", "finn","fins","fire","firm","fish","fist","fits","five","flag","flak", "flap","flat","flaw","flea","fled","flee","flew","flex","flip","flop", "flow","floy","flue","flux","foal","foam","foci","foes","foil","fold", "folk","fond","font","food","fool","foot","ford","fore","fork","form", "fort","foul","four","fowl","fran","frau","fray","fred","free","fret", "frog","from","ftse","fuel","fuji","full","fund","funk","furs","fury", "fuse","fuss","fyfe","gael","gail","gain","gait","gala","gale","gall", "game","gang","gaol","gaps","garb","gary","gash","gasp","gate","gatt", "gaul","gave","gays","gaza","gaze","gcse","gear","gels","gems","gene", "gens","gent","germ","gets","gift","gigs","gill","gilt","gina","girl", "gist","give","glad","glee","glen","glow","glue","glum","goal","goat", "gods","goes","goff","gogh","gold","golf","gone","good","gore","gory", "gosh","gown","grab","graf","gram","gran","gray","greg","grew","grey", "grid","grim","grin","grip","grit","grow","grub","guil","gulf","gull", "gulp","gums","gunn","guns","guru","gust","guts","guys","gwen","hack", "haig","hail","hair","hale","half","hall","halo","halt","hams","hand", "hang","hank","hans","hard","hare","hari","harm","harp","hart","hash", "hate","hath","hats","hatt","haul","have","hawk","haze","hazy","head", "heal","heap","hear","heat","heck","heed","heel","heir","hela","held", "hell","helm","help","hens","herb","herd","here","hero","herr","hers", "hess","hibs","hick","hide","high","hike","hill","hilt","hind","hint", "hips","hire","hiss","hits","hive","hiya","hmso","hoax","hogg","hold", "hole","holt","holy","home","hong","hons","hood","hoof","hook","hoop", "hope","hops","horn","hose","host","hour","hove","howe","howl","hrun", "hues","huge","hugh","hugo","hulk","hull","hume","hump","hung","hunt", "hurd","hurt","hush","huts","hyde","hype","iaea","iago","iain","ibid", "iboa","iced","icon","idea","idle","idly","idol","igor","ills","inca", "ince","inch","info","inns","insp","into","iona","ions","iowa","iran", "iraq","iris","iron","isis","isle","itch","item","ivan","ives","ivor", "jack","jade","jail","jake","jams","jane","jars","java","jaws","jazz", "jean","jeep","jeff","jerk","jess","jest","jets","jett","jews","jill", "jimi","joan","jobs","jock","joel","joey","john","join","joke","jolt", "jose","josh","joys","juan","judd","jude","judi","judo","judy","jugs", "july","jump","june","jung","junk","jury","just","kahn","kane","kant", "karl","karr","kate","kath","katy","katz","kaye","keel","keen","keep", "kemp","kent","kept","kerb","kerr","keys","khan","kick","kidd","kids", "kiev","kiff","kill","kiln","kilo","kilt","kind","king","kirk","kiss", "kite","kits","kiwi","knee","knew","knit","knob","knot","know","knox", "koch","kohl","kong","kuhn","kurt","kyle","kyte","labs","lace","lack", "lacy","lads","lady","laid","lain","lair","lais","lake","lama","lamb", "lame","lamp","land","lane","lang","laos","laps","lard","lark","lass", "last","late","lava","lawn","laws","lays","lazy","lead","leaf","leak", "lean","leap","lear","leas","lech","lees","left","legs","lend","lens", "lent","leon","less","lest","lets","levi","levy","leys","liam","liar", "lice","lick","lids","lied","lien","lies","life","lift","like","lili", "lily","lima","limb","lime","limp","lina","line","ling","link","lino", "lion","lips","lira","lire","lisa","list","live","liza","load","loaf", "loan","lobe","loch","lock","loco","loft","logo","logs","lois","lone", "long","look","loom","loop","loos","loot","lord","lore","lori","lose", "loss","lost","lots","loud","love","lowe","ltte","luce","luch","luck", "lucy","ludo","luis","luke","lull","lump","lung","lure","lush","lust", "lute","lyle","lyon","mabs","mace","mach","mack","made","maid","mail", "main","mait","make","mala","male","mali","mall","malt","mama","mane", "mann","mans","manx","many","maps","marc","mare","mark","marr","mars", "marx","mary","mash","mask","mass","mast","mate","mats","matt","maud", "mayo","maze","mead","meal","mean","meat","meek","meet","mega","melt", "memo","mend","mens","menu","mere","mesh","mess","mice","mick","midi", "mike","mild","mile","milk","mill","mime","mind","mine","minh","mini", "mink","mins","mint","mips","mira","mire","miss","mist","mite","moan", "moat","mobs","moby","mock","mode","modi","mold","mole","mona","monk", "mono","mont","mood","moon","moor","moot","more","mori","moss","most", "moth","mott","move","mrna","much","muck","mugs","muir","mule","mull", "mums","muon","muse","must","mute","myra","nacl","naff","nail","name", "nana","nape","nasa","nash","nato","nave","navy","neal","near","neat", "neck","need","neil","nell","neon","nero","ness","nest","nets","news", "next","nice","nick","niki","nile","nina","nine","niro","noah","node", "nods","noel","noir","nome","nona","none","noon","nope","nora","norm", "nose","note","noun","nova","nowt","nude","null","numb","nunn","nuns", "nupe","nuts","oaks","oars","oath","oats","oban","obey","oboe","odds", "oecd","offa","ohio","ohms","oils","oily","okay","olds","olga","oman", "omar","omen","omit","once","ones","only","onto","onus","oops","opal", "opcs","opec","open","oral","orcs","ores","orgy","oslo","otto","ould", "ours","oust","outs","oval","oven","over","owed","owen","owes","owls", "owns","oxen","pace","pack","pact","pads","page","pahl","paid","pain", "pair","pale","pall","palm","pals","pane","pang","pans","papa","para", "park","parr","part","pass","past","pate","path","paul","pave","pawn", "paws","pays","peak","pear","peas","peat","peck","peel","peer","pegs", "peng","penh","penn","pens","pepe","perm","pers","pert","peru","pest", "pete","pets","pews","phew","phil","pick","pied","pier","pies","pigs", "pike","pile","pill","pine","ping","pink","pins","pint","pipe","pips", "pisa","piss","pits","pitt","pity","pius","plan","play","plea","plot", "ploy","plug","plum","plus","pods","poem","poet","poke","pole","poll", "polo","poly","pomp","pond","pons","pont","pony","pooh","pool","poor", "pope","pops","pore","pork","porn","port","pose","posh","posi","post", "pots","pour","pram","prat","pray","prep","pres","prey","prim","prix", "prof","prop","pros","prow","pubs","puff","pugh","pull","pulp","pump", "punk","punt","puny","pups","pure","push","puts","putt","quay","quid", "quit","quiz","race","rack","racy","raft","rage","rags","raid","rail", "rain","rake","ramp","rams","rang","rank","rape","rapt","rare","rash", "rate","rats","rave","rays","rbge","rdbi","read","real","reap","rear", "reds","reed","reef","reel","rees","refs","reid","rein","rely","rene", "rent","reps","rest","retd","revd","revs","reza","rhee","riba","ribs", "rica","rice","rich","rick","rico","ride","rife","rift","riga","rigs", "rind","ring","rink","riot","ripe","risc","rise","risk","rita","rite", "ritz","riva","rnli","road","roam","roar","robb","robe","rock","rode", "rods","role","rolf","roll","roma","rome","roof","rook","room","root", "rope","rory","rosa","rose","ross","rosy","rota","roth","rout","rowe", "rows","rubs","ruby","ruck","rudd","rude","rugs","ruin","rule","rump", "rune","rung","runs","ruse","rush","russ","rust","ruth","ryan","sack", "safe","saga","sage","said","sail","sake","sale","salt","same","sand", "sane","sang","sank","sans","sara","sash","saul","save","saws","says", "sbus","scan","scar","scot","scsi","scum","seal","seam","sean","seas", "seat","secs","sect","seed","seek","seem","seen","seep","sees","sega", "sejm","self","sell","sema","semi","send","sent","sept","sera","serb", "serc","seth","sets","seve","sewn","sexy","shae","shah","shai","sham", "shaw","shed","shia","shih","shin","ship","shoe","shop","shot","show", "shut","sick","side","sigh","sign","sikh","silk","sill","silt","sims", "sine","sing","sink","sins","site","sits","size","skin","skip","skis", "skye","slab","slag","slam","slap","slid","slim","slip","slit","slot", "slow","slug","slum","slur","slut","smog","smug","snag","snap","snip", "snob","snow","snub","snug","soak","soap","soar","sobs","sock","soda", "sofa","soft","soho","soil","sold","sole","solo","some","song","sons", "sony","soon","soot","sore","sort","soul","soup","sour","sown","sows", "soya","span","spar","spat","spec","sped","spin","spit","spot","spun", "spur","ssap","stab","stag","stan","star","stay","stem","step","stew", "stir","stok","stop","stow","stub","stud","subs","such","suck","sued", "suez","suit","sums","sung","sunk","suns","supt","sure","surf","suzi", "suzy","swam","swan","swap","sway","swig","swim","tabs","tack","tact", "taff","tags","tail","tait","take","tale","talk","tall","tame","tang", "tank","tape","taps","tara","tart","task","tate","taut","taxi","teak", "teal","team","tear","teas","tech","tecs","teen","tees","tell","tend", "tens","tent","term","tess","test","text","thai","than","that","thaw", "thee","them","then","theo","they","thin","this","thou","thud","thug", "thus","tick","tide","tidy","tied","tier","ties","tile","till","tilt", "time","tina","tins","tiny","tips","tire","tito","toad","toby","todd", "toes","togo","toil","told","toll","tomb","tome","tone","toni","tons", "tony","took","tool","tops","tore","torn","tort","tory","toss","tour", "town","toys","tram","trap","tray","tree","trek","trim","trio","trip", "trna","trod","trot","troy","true","tsar","tube","tubs","tuck","tuna", "tune","tung","turf","turk","turn","tvei","twig","twin","twit","twos", "tyne","type","tyre","ucta","uefa","ugly","uist","undo","unit","unix", "unto","upon","urea","urge","urgh","used","user","uses","ussr","utah", "vain","vale","vane","vans","vary","vase","vass","vast","vats","veal", "veil","vein","vent","vera","verb","vern","very","vest","veto","vets", "vial","vibe","vice","view","vile","vine","visa","vita","vivo","void", "vole","volt","vote","vous","vows","wabi","wacc","wade","wage","wail", "wait","wake","walk","wall","walt","wand","wang","want","ward","ware", "warm","warn","warp","wars","wary","wash","wasp","watt","wave","wavy", "ways","weak","wear","webb","webs","weed","week","weep","weir","well", "went","wept","were","west","what","when","whig","whim","whip","whit", "whoa","whom","wick","wide","wife","wigs","wild","will","wily","wind", "wine","wing","wink","wins","wipe","wire","wiry","wise","wish","with", "wits","woes","woke","wolf","womb","wont","wood","wool","word","wore", "work","worm","worn","wove","wrap","wren","writ","wyre","yale","yang", "yard","yarn","yawn","yeah","year","yell","yoga","yoke","yolk","york", "your","yous","yuan","yuri","yves","zach","zack","zapt","zeal","zero", "zest","zeta","zeus","zinc","zone","zoom","zoos","zzap" }; int debug = 0; /* add the output and time of a shell command to message digest */ void gurgle(md_state *mdp, char *command) { FILE *f; char buf[128]; long len = 0, l; struct timeval t; f = popen(command, "r"); gettimeofday(&t, NULL); md_add(mdp, (unsigned char *) &t, sizeof(t)); if (!f) { fprintf(stderr, "External entropy source command '%s'\n" "(one of several) failed.\n", command); return; } while (!feof(f) && !ferror(f)) { len += l = fread(buf, 1, sizeof(buf), f); md_add(mdp, buf, l); } if (len == 0) fprintf(stderr, "External entropy source command '%s'\n" "returned no output.\n", command); else if (debug) fprintf(stderr, "'%s' added %ld bytes.\n", command, len); pclose(f); gettimeofday(&t, NULL); md_add(mdp, (unsigned char *) &t, sizeof(t)); } /* A random bit generator. Hashes together various sources of entropy * to provide a 16 byte high quality random seed */ /* Determine the initial start state of the random bit generator */ void rbg_seed(unsigned char *r) { /* shell commands that provide high entropy output for RNG */ char *entropy_cmds[] = { ENTROPY_CMDS }; char *entropy_env[] = { ENTROPY_ENV }; unsigned i; md_state md; struct { clock_t clk; pid_t pid; uid_t uid; pid_t ppid; } entropy; md_init(&md); /* get entropy via some shell commands */ for (i = 0; i < sizeof(entropy_env)/sizeof(char*); i++) putenv(entropy_env[i]); for (i = 0; i < sizeof(entropy_cmds)/sizeof(char*); i++) gurgle(&md, entropy_cmds[i]); /* other minor sources of entropy */ entropy.clk = clock(); entropy.uid = getuid(); entropy.pid = getpid(); entropy.ppid = getppid(); md_add(&md, (unsigned char *) &entropy, sizeof(entropy)); md_close(&md, r); } /* Determine the next random bit generator state */ void rbg_iter(unsigned char *r) { md_state md; struct timeval t; md_init(&md); gettimeofday(&t, NULL); md_add(&md, (unsigned char *) &t, sizeof(t)); md_add(&md, r, MD_LEN); md_add(&md, "AutomaGic", 9); /* feel free to change this as a site key */ md_close(&md, r); } /* * Transform the first 6*chars bits of the binary string v into a chars * character long string s. The encoding is a modification of the MIME * base64 encoding where characters with easily confused glyphs are * avoided (0 vs O, 1 vs. l vs. I). */ void conv_base64(char *s, const unsigned char *v, int chars) { static const char tab[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk%mnopqrstuvwxyz" ":=23456789+/"; int i, j; for (i = 0; i < chars; i++) { j = (i / 4) * 3; switch (i % 4) { case 0: *s++ = tab[ v[j] >>2]; break; case 1: *s++ = tab[((v[j] <<4) & 0x30) | (v[j+1]>>4)]; break; case 2: *s++ = tab[((v[j+1]<<2) & 0x3c) | (v[j+2]>>6)]; break; case 3: *s++ = tab[ v[j+2] & 0x3f]; break; } } *s++ = '\0'; } /* * Normalize a password by removing whitespace etc. and converting * l1| -> I, 0 -> O, \ -> /, just like otpw_verify() does */ void pwnorm(char *password) { char *src, *dst; src = dst = password; while (1) { if (*src == 'l' || *src == '1' || *src == '|') *dst++ = 'I'; else if (*src == '0') *dst++ = 'O'; else if (*src == '\\') *dst++ = '/'; else if ((*src >= 'A' && *src <= 'Z') || (*src >= 'a' && *src <= 'z') || (*src >= '2' && *src <= '9') || *src == ':' || *src == '%' || *src == '=' || *src == '+' || *src == '/') *dst++ = *src; else if (*src == '\0') { *dst++ = *src; return; } src++; } } #define PW_BASE64 0 #define PW_WORD4 1 /* * Convert a random bit sequence into a printable password * * Input: v random bit string * vlen length of v in bytes * type 0: modified base-64 encoding * entropy requested minimum entropy of password * buf buffer for returning zero-terminated output password * buflen length of buffer in bytes * * Returns negative value if provided combination of vlen, type, * ent and buflen are not adequate, otherwise return length of * generated password (excluding terminating '\0'). * * If buf == NULL, return value depends on buflen: * * 0: length of password that would have been generated * 1: number of its non-space password characters * 2: actually used entropy if buflen == 2 * 3: maximum entropy that can be specified for given vlen */ int make_passwd(const unsigned char *v, int vlen, int type, int entropy, char *buf, int buflen) { int pwchars; /* number of characters in password */ int pwlen; /* length of password, including whitespace */ int emax; int i, j, k; /* calculate length of output and actually used entropy */ switch (type) { case PW_BASE64: pwchars = (entropy + 5) / 6; entropy = pwchars * 6; pwlen = pwchars + (pwchars > 5 ? (pwchars - 1) / 4 : 0); emax = ((vlen * 8) / 6) * 6; break; case PW_WORD4: pwchars = 4 * ((entropy + 10) / 11); entropy = 11 * ((entropy + 10) / 11); pwlen = pwchars + pwchars / 4 - (pwchars > 0); emax = ((vlen * 8) / 11) * 11; break; default: return -1; } if (!buf) { switch (buflen) { case 0: return pwlen; /* including spaces */ case 1: return pwchars; /* excluding spaces */ case 2: return entropy; case 3: return emax; default: return -2; } } if (entropy > vlen * 8) return -3; if (pwlen >= buflen) return -4; switch (type) { case PW_BASE64: conv_base64(buf, v, pwchars); /* add spaces every 3-4 chars for readability (Bresenham's algorithm) */ i = pwchars - 1; j = pwlen - 1; k = (pwlen - pwchars) / 2; while (i >= 0 && j >= 0) { buf[j--] = buf[i--]; if ((k += pwlen - pwchars + 1) >= pwchars && j > 0) { buf[j--] = ' '; k -= pwchars; } } buf[pwlen] = '\0'; break; case PW_WORD4: for (i = 0; i < pwchars/4; i++) { k = 0; for (j = i * 11; j < (i+1) * 11; j++) k = (k << 1) | ((v[j / 8] >> (j % 8)) & 1); memcpy(buf + i * 5, word[k], 4); buf[i * 5 + 4] = ' '; } buf[i * 5 - 1] = '\0'; break; default: return -1; } assert((int) strlen(buf) == pwlen); return pwlen; } int main(int argc, char **argv) { char version[] = "One-Time Password Generator v 1.2 -- Markus Kuhn"; char usage[] = "%s\n\n%s [options] | lpr\n" "\nOptions:\n\n" "\t-h \tnumber of output lines (default 60)\n" "\t-w \tmax width of output lines (default 79)\n" "\t-s \tnumber of output pages (default 1)\n" "\t-e \tminimum entropy of each one-time password [bits]\n" "\t\t\t(low security: <30, default: 48, high security: >60)\n" "\t-p0\t\tpasswords from modified base64 encoding (default)\n" "\t-p1\t\tpasswords from English 4-letter words\n" "\t-f \tdestination file for hashes (default: ~/" OTPW_FILE ")\n" "\t-d\t\toutput debugging information\n"; unsigned char r[MD_LEN], h[MD_LEN]; md_state md; int i, j, k, l; struct passwd *pwd = NULL; FILE *f; char timestr[81], hostname[81], password1[81], password2[81]; char header[LINE_MAX]; char *fnout = NULL, *fnoutp = ""; struct termios term, term_old; int stdin_is_tty = 0; int width = 79, rows = 60 - HEADER_LINES, pages = 1, pwlen, pwchars; int entropy = 48, emax, type = PW_BASE64; int cols; time_t t; char *hbuf; assert(md_selftest() == 0); assert(OTPW_HLEN * 6 < MD_LEN * 8); assert(OTPW_HLEN >= 8); /* read command line arguments */ for (i = 1; i < argc; i++) { if (argv[i][0] == '-') for (j = 1; j > 0 && argv[i][j] != 0; j++) switch (argv[i][j]) { case 'h': if (++i >= argc) { fprintf(stderr, "Specify number of lines output after option -h " "(e.g., \"-h 50\")!\n"); exit(1); } rows = atoi(argv[i]) - HEADER_LINES; if (rows <= 0) { fprintf(stderr, "Specify not less than %d lines " "(to leave room for header)!\n", HEADER_LINES + 1); exit(1); } j = -1; break; case 'w': if (++i >= argc) { fprintf(stderr, "Specify maximum line length after option -w " "(e.g., \"-l 50\")!\n"); exit(1); } width = atoi(argv[i]); if (width < 64) { fprintf(stderr, "Specify not less than 64 character " "wide lines!\n"); exit(1); } j = -1; break; case 's': if (++i >= argc) { fprintf(stderr, "Specify number of pages after option -s " "(e.g., \"-s 2\")!\n"); exit(1); } pages = atoi(argv[i]); if (pages < 1) { fprintf(stderr, "Specify at least 1 page!\n"); exit(1); } j = -1; break; case 'e': if (++i >= argc) { fprintf(stderr, "Specify minimum entropy (bits) after option -e " "(e.g., \"-e 64\")!\n"); exit(1); } entropy = atoi(argv[i]); j = -1; break; case 'p': if (strlen(argv[i]+j) == 2 && argv[i][j+1] >= '0' && argv[i][j+1] <= '1') type = argv[i][j+1] - '0'; else { fprintf(stderr, "Unknown password format option '-%s'!\n", argv[i]+j); exit(1); } j = -1; break; case 'f': if (++i >= argc) { fprintf(stderr, "Specify filename after option -f!\n"); exit(1); } fnout = argv[i]; j = -1; break; case 'd': debug = 1; break; default: fprintf(stderr, usage, version, argv[0]); exit(1); } else { fprintf(stderr, usage, version, argv[0]); exit(1); } } /* determine password length */ pwlen = make_passwd(NULL, MD_LEN/2, type, entropy, NULL, 0); pwchars = make_passwd(NULL, MD_LEN/2, type, entropy, NULL, 1); /* check whether entropy is ok */ emax = make_passwd(NULL, MD_LEN/2, type, entropy, NULL, 3); if (entropy < EMIN || entropy > emax) { fprintf(stderr, "Entropy must be in the range %d to %d bits!\n", EMIN, emax); exit(1); } cols = (width + 2) / (CHALLEN + 1 + pwlen + 2); if (pages * rows * cols > 1000) { if (pages == 1) rows = 1000 / cols; } if (debug) fprintf(stderr, "pwlen=%d, pwchars=%d, emax=%d, cols=%d, rows=%d\n", pwlen, pwchars, emax, cols, rows); if (!fnout) { fnout = OTPW_FILE; pwd = getpwuid(getuid()); if (!pwd) { fprintf(stderr, "Can't access your password database entry!\n"); exit(1); } /* change to home directory */ if (chdir(pwd->pw_dir) == 0) fnoutp = "~/"; } fprintf(stderr, "Generating random seed ...\n"); rbg_seed(r); fprintf(stderr, "\nIf your paper password list is stolen, the thief should not gain\n" "access to your account with this information alone. Therefore, you\n" "need to memorize and enter below a prefix password. You will have to\n" "enter that each time directly before entering the one-time password\n" "(on the same line).\n\n" "When you log in, a %d-digit password number will be displayed. It\n" "identifies the one-time password on your list that you have to append\n" "to the prefix password. If another login to your account is in progress\n" "at the same time, several password numbers may be shown and all\n" "corresponding passwords have to be appended after the prefix\n" "password. Best generate a new password list when you have used up half\n" "of the old one.\n\n", CHALLEN); /* disable echo if stdin is a terminal */ if (!tcgetattr(fileno(stdin), &term)) { stdin_is_tty = 1; term_old = term; term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); if (tcsetattr(fileno(stdin), TCSAFLUSH, &term)) { perror("tcsetattr"); exit(1); } } /* check whether there is an old password list, to warn against * accidental overwriting */ f = fopen(fnout, "r"); if (f) { fclose(f); fprintf(stderr, "Overwrite existing password list '%s%s' (Y/n)? ", fnoutp, fnout); fgets(password1, sizeof(password1), stdin); if (password1[0] != '\n' && password1[0] != 'y' && password1[0] != 'Y') { if (stdin_is_tty) tcsetattr(fileno(stdin), TCSANOW, &term_old); fprintf(stderr, "\nAborted.\n"); exit(1); } fprintf(stderr, "\n\n"); } /* ask for prefix password */ fprintf(stderr, "Enter new prefix password: "); fgets(password1, sizeof(password1), stdin); fprintf(stderr, "\nReenter prefix password: "); fgets(password2, sizeof(password2), stdin); if (stdin_is_tty) tcsetattr(fileno(stdin), TCSANOW, &term_old); if (strcmp(password1, password2)) { fprintf(stderr, "\nThe two entered passwords were not identical!\n"); exit(1); } /* remove newline = last character */ if (*password1) password1[strlen(password1)-1] = 0; fprintf(stderr, "\n\nCreating '%s%s'.\n", fnoutp, fnout); f = fopen(OTPW_TMP, "w"); if (!f) { fprintf(stderr, "Can't write to '" OTPW_TMP); perror("'"); exit(1); } chmod(OTPW_TMP, S_IRUSR | S_IWUSR); /* write magic code for format identification */ fprintf(f, OTPW_MAGIC); fprintf(f, "%d %d %d %d\n", pages * rows * cols, CHALLEN, OTPW_HLEN, pwchars); fprintf(stderr, "Generating new one-time passwords ...\n\n"); /* prepare header line that uniquely identifies this password list */ time(&t); strftime(timestr, 80, "%Y-%m-%d %H:%M", localtime(&t)); strcpy(hostname, "???"); gethostname(hostname, sizeof(hostname)); hostname[sizeof(hostname)-1] = 0; snprintf(header, sizeof(header), "OTPW list generated %s on %s" NL NL, timestr, hostname); hbuf = malloc(pages * rows * cols * HBUFLEN); if (!hbuf) { fprintf(stderr, "Memory allocation error!\n"); exit(1); } for (l = 0; l < pages; l++) { fputs(header, stdout); for (i = 0; i < rows; i++) { for (j = 0; j < cols; j++) { k = j * rows + i + l * rows * cols; /* generate new password */ rbg_iter(r); make_passwd(r, MD_LEN, type, entropy, password2, sizeof(password2)); /* output challenge */ printf("%03d %s", k, password2); printf(j == cols - 1 ? NL : " "); /* hash password1 + pwnorm(password2) and save result */ md_init(&md); md_add(&md, password1, strlen(password1)); pwnorm(password2); md_add(&md, password2, pwchars); md_close(&md, h); sprintf(hbuf + k * HBUFLEN, "%0*d", CHALLEN, k); conv_base64(hbuf + k*HBUFLEN + CHALLEN, h, OTPW_HLEN); } } printf(NL "%*s%s", (cols*(CHALLEN + 1 + pwlen + 2) - 2)/2 + 50/2, "!!! REMEMBER: Enter the PREFIX PASSWORD first !!!", l == pages - 1 ? NL : FF); } /* paranoia RAM scrubbing (note that we can't scrub stdout/stdin portably) */ md_init(&md); md_add(&md, "Always clean up all memory that was in contact with secrets!!!!!!", 65); md_close(&md, h); memset(password1, 0xaa, sizeof(password1)); memset(password2, 0xaa, sizeof(password2)); /* output all hash values in random permutation order */ for (k = pages * rows * cols - 1; k >= 0; k--) { rbg_iter(r); i = k > 0 ? (*(unsigned *) r) % k : 0; fprintf(f, "%s\n", hbuf + i*HBUFLEN); memcpy(hbuf + i*HBUFLEN, hbuf + k*HBUFLEN, HBUFLEN); } fclose(f); if (rename(OTPW_TMP, fnout)) { fprintf(stderr, "Can't rename '" OTPW_TMP "' to '%s", fnout); perror("'"); exit(1); } /* if we overwrite OTPW_FILE, then any remaining lock is now meaningless */ if (pwd) unlink(OTPW_LOCK); return 0; } otpw/otpw-gen.10100644000307500030750000001042107736356666012360 0ustar mgk25mgk25.TH OTPW-GEN 1 "2003-09-30" .SH NAME otpw-gen \- one-time password generator .SH SYNOPSIS .B otpw-gen [ .I options ] .SH DESCRIPTION .I OTPW is a one-time password authentication system. It can be plugged into any application that needs to authenticate users interactively. One-time password authentication is a valuable protection against password eavesdropping, especially for logins from untrusted terminals. Before you can use .I OTPW to log into your system, two preparation steps are necessary. Firstly, your system administrator has to enable it. (This is usually done by configuring your login software (e.g., sshd) to use .I OTPW via the Pluggable Authentication Module (PAM) configuration files in /etc/pam.d/.) Secondly, you need to generate a list of one-time passwords and print it out. This can be done by calling .IP .B otpw-gen | lpr .PP or something like .IP .B otpw-gen -h 70 -s 2 | a2ps -1B -L 70 --borders no .PP if more control over the layout is desired. You will be asked for a .IR "prefix password" , which you need to memorize. It has to be entered immediately before the one-time password. The prefix password reduces the risk that anyone who finds or steals your password printout can use that alone to impersonate you. Each one-time password will be printed behind a three digit password number. Such a number will appear in the password prompt when .I OTPW has been activated: .IP .B Password 026: .PP When you see this prompt, enter the memorized prefix password, followed immediately by the one-time password identified by the number. Any spaces within a password have only been inserted to improve legibility and do not have to be copied. .I OTPW will ignore the difference between the easily confused characters .B 0O and .B Il1 in passwords. In some situations, for example if multiple logins occur simultaneously for the same user, .I OTPW defends itself against the possibility of various attacks by asking for three random passwords simultaneously. .IP .B Password 047/192/210: .PP You then have to enter the prefix password, followed immediately by the three requested one-time passwords. This fall-back mode is activated by the existence of the lock file .BR ~/.otpw.lock . If it was left over by some malfunction, it can safely be deleted manually. Call .B otpw-gen again when you have used up about half of the printed one-time passwords or when you have lost your password sheet. This will disable all remaining passwords on the previous sheet. .SH OPTIONS .TP 14 .BI \-h " number" Specify the total number of lines per page to be sent to standard output. This number minus four header lines determines the number of rows of passwords on each page. The maximum number of passwords that can be printed is 1000. (Minimum: 5, default: 60) .TP .BI \-w " number" Specify the maximum width of lines to be sent to standard output. This parameter determines together with the password length the number of columns in the printed password matrix. (Minimum: 64, default: 79) .TP .BI \-s " number" Specify the number of form-feed separated pages to be sent to standard output. (Default: 1) .TP .BI \-e " number" Specify the minimum entropy of each one-time password in bits. The length of each password will be chosen automatically, such that there are at least two to the power of the specified number possible passwords. A value below 30 might make the passwords vulnerable to a brute-force guessing attack. If the attacke might have read access to the .B ~/.otpw file, the value should be at least 48. Paranoid users might prefer long high-security passwords with at least 60 bits of entropy. (Default: 48) .TP .BI \-p0 Generate passwords by transforming a random bit string into a sequence of letters and digits, using a form of base-64 encoding (6 bits per character). (Default) .TP .BI \-p1 Generate passwords by transforming a random bit string into a sequence of English four-letter words, each chosen from a fixed list of 2048 words (2.75 bits per character). .TP .BI \-f " filename" Specify a file to be used instead of .I ~/.otpw for storing the hash values of the generated one-time passwords. .SH AUTHOR The .I OTPW package, which includes the .I otpw-gen progam, has been developed by Markus Kuhn. The most recent version is available from . .SH SEE ALSO pam(8), pam_otpw(8) otpw/ttt0100600000307500030750000000172007736354022011246 0ustar mgk25mgk25OTPW1 20 3 12 8 042XFhOBrQ9BwdX 041O=a=jtyE2c4W 007i7upvPmx5wIw 006==KIJsd28Oa2 026ATBZg6xtAkmC 025vt=SXkkaExBC 018eM2hVZqw%QDf 058H8VsWJYvMgg= 02052nD8cA=73Q/ 036hIDAuhvUxujh 024hXINXju6eoYj 013vonr7MHU8S/E 028gHWdi4Psrn78 040EbkyviRAUNIM 014YgN:g/ctp6sc 016Ju6fFBMixigk 004/h/Rbp4c6MEX 055ALgL+BUAaLB9 015jAc=SU73J2Ck 056sNVLiRaQtoAK 030uS=fWcum6eFa 032S9hBSo7jAAkX 031/T6SXxvYvWS% 039czayRFYbzJNx 002o%LVC6Un:LNq 008hyY=/BbEVpyc 047xSz/Z4O5f%K/ 027oPO=aYAmyRSF 059swcyGYjNnY%q 034gxYvDrcus7Sh 009QmkG8UmcvXgE 044A/fkB/vWRxFc 021oWypCf65rcfh 0017BHqzWGUuUnv 051oUr/hqIcyMNy 022jeH%7+6J2szT 010aWI%teB+57HS 011WHQPmAUy3+Ue 043x8:bVvPmgLcV 000UA57=mjqB%7H 057S:CsUaK2xY9S 037ir4XTnJhVFWr 019FajqCt:Z7vou 052cS2yEvPYx2TY 053dsOihzInFwXo 029pXpnQgFBoD4n 035=H9aN7kRakBx 048+9fJr%IzmuiP 017LcnhPgskRYrf 045uOLoq7a3CKJ6 0548dHfxGbje/mq 038e:ZX%%IeNUd= 012/IkG5ya8nt24 005cccnB+fuZsGx 050R72myNSHfFT2 049o3ENPfGVIUyB 046twYLXwvohDvJ 023CiueSLRDMoYd 003aFdiZ/CCPj3K 033rdcWyqE5J9OF otpw/CHANGES0100664000307500030750000000123207736361503011515 0ustar mgk25mgk25 OTPW revision history --------------------- Changes in version 1.1 (2003-06-19) - added pam_otpw module (with assistance from Steven Murdoch) Changes in version 1.2 (2003-08-31) - added English 4-letter-word password option - converted several compile-time options into run-time options Changes in version 1.3 (2003-09-30) - added new option -s to otpw-gen - added code to otpw.c to handle a comment line starting with '#' as the second line (this is not yet used by otpw-gen) - fixed a string manipulation bug (not security critical) in otpw.c (reported by Nicolas Pougetoux) - a few more minor bug fixes and stylistic improvements otpw/otpw.h0100644000307500030750000000616107740300202011650 0ustar mgk25mgk25/* * One-time password login capability * * Markus Kuhn * * $Id: otpw.h,v 1.5 2003-06-24 21:41:22+01 mgk25 Rel $ */ #ifndef OTPW_H #define OTPW_H #include #include #include "conf.h" #include "md.h" /* password authentication results (returned by otpw_verify()) */ #define OTPW_OK 0 /* this was the correct password */ #define OTPW_WRONG 1 /* this was the wrong password */ #define OTPW_ERROR 2 /* user has not registered for the OTPW service * or something else went wrong */ /* flags for otpw_prepare() */ #define OTPW_DEBUG 1 /* output debugging messages via DEBUG_LOG macro */ #define OTPW_NOLOCK 2 /* disable locking, never create or check OTPW_LOCK */ /* * A data structure used by otpw_prepare to return the * selected challenge */ struct challenge { char challenge[81]; /* print this string before "Password:" */ int passwords; /* number of req. passwords (0, 1, OTPW_MULTI) */ int locked; /* flag, whether lock has been set */ int entries; /* number of entries in OTPW_FILE */ int pwlen; /* number of characters in password */ int remaining; /* number of remaining unused OTPW_FILE entries */ uid_t uid; /* effective uid for OTPW_FILE/OTPW_LOCK access */ gid_t gid; /* effective gid for OTPW_FILE/OTPW_LOCK access */ int selection[OTPW_MULTI]; /* positions of the requested passwords */ char hash[OTPW_MULTI][OTPW_HLEN + 1]; /* base64 hash value of the requested passwords */ int flags; /* 1 : debug messages, 2: no locking */ }; /* * After the user has entered a login name and has requested OTPW * authentication and after and you have retrieved the password * database entry *user for this name, call otpw_prepare(). * A string with the challenge text that has to be presented to the * user before the password can be entered will be found in * ch->challenge afterwards, but ch->challenge[0] == 0 if one-time * password authentication is not possible at this time. The struct *ch * has to be given later to otpw_verify(). We do a chdir() to the user's * home directory here, and otpw_verify() will expect the * current working directory to still be there, so don't change it * between the two calls. After a successful login, check whether * ch->entries > 2 * ch->remaining and remind the user to generate * new passwords if so. */ void otpw_prepare(struct challenge *ch, struct passwd *user, int flags); /* * After the one-time password has been entered, call optw_verify() * to find out whether the password was ok. The parameters are * the challenge structure filled previously by otpw_prepare() and * the entered password. Accept the user iff the return value is OTPW_OK. * * IMPORTANT: If otpw_prepare() has returned a non-empty challenge * string, then you must call otpw_verify(), even if the login was * aborted and you are not any more interested in the result. Otherwise * a stale lock might remain. */ int otpw_verify(struct challenge *ch, char *password); #endif otpw/otpw-l.c0100644000307500030750000000123307740300202012067 0ustar mgk25mgk25/* * One-time password login capability * * This file is just a variant of otpw.c, in which the debugging * messages are directed to the log_message function of pam_otpw.c * instead of to stderr. Link otpw-l.o instead of otpw.o into * pam_otpw.so for debugging purposes. * * Markus Kuhn * * $Id: otpw-l.c,v 1.5 2003-08-31 21:51:34+01 mgk25 Rel $ */ #include #ifndef DEBUG_LOG extern void log_message(int priority, void *pamh, const char *format, ...); #define DEBUG_LOG(...) if (ch->flags & OTPW_DEBUG) \ log_message(LOG_DEBUG, (void *) 0, __VA_ARGS__) #endif #include "otpw.c" otpw/pam_otpw.80100644000307500030750000000311707736356363012453 0ustar mgk25mgk25.TH PAMOTPW 1 "2003-09-30" .SH NAME pam_otpw \- verify one-time passwords .SH SYNOPSIS .B pam_otpw [ .I arguments ] .SH DESCRIPTION .I OTPW is a one-time password authentication system. It compares entered passwords with hash values stored in the user's home directory in the file .BR ~/.otpw . Once a password was entered correctly, its hash value in .B ~/.otpw will be overwritten with hyphens, which disables its use in future authentication. A lock file .B ~/.otpw.lock prevents that the same password challenge is issued on several concurrent authentication sessions. This helps to prevent an eavesdropper from copying a one-time password as it is entered instantly into a second session, in the hope to get access by sending the final newline character faster than the user could. Both an authentication management and a session management function are offered by this module. The authentication function asks for and verifies one-time passwords. The session function prints a message after login that reminds the user of the remaining number of one-time passwords. .SH ARGUMENTS .IP debug Turn on debugging via \fBsyslog(3)\fR. .IP nolock Disable locking. This option tells the authentication function of .I pam_otpw.so to ignore any existing .B ~/.otpw.lock lock file and not to generate any. With this option, .I pam_otpw.so will never ask for several passwords simultaneously. .SH AUTHOR The .I OTPW package, which includes the .I otpw-gen progam, has been developed by Markus Kuhn. The most recent version is available from . .SH SEE ALSO otpw-gen(1), pam(8) otpw/pam_otpw.c0100644000307500030750000002412207740300205012500 0ustar mgk25mgk25/* * One-time password login capability * * Markus Kuhn * Steven Murdoch * * Interface documentation: * * http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/pam_modules.html * http://www.cl.cam.ac.uk/~mgk25/otpw.html * * Inspired by pam_pwdfile.c by Charl P. Botha * and pam_unix/support.c (part of the standard PAM distribution) * */ static char const rcsid[] = "$Id: pam_otpw.c,v 1.6 2003-09-01 16:53:55+01 mgk25 Rel $"; #include #include #include #include #include #include #include #define PAM_SM_AUTH #define PAM_SM_SESSION #include #include "otpw.h" #define D(a) if (debug) { a; } #define MODULE_NAME "pam_otpw" /* * Output logging information to syslog * * pamh pointer to the PAM handle * priority, format, ... passed on to (v)syslog * * (based on _log_err in pam_unix/support.c) */ void log_message(int priority, pam_handle_t *pamh, const char *format, ...) { char *service = NULL; char logname[80]; va_list args; if (pamh) pam_get_item(pamh, PAM_SERVICE, (const void **) &service); if (!service) service = ""; snprintf(logname, sizeof(logname), "%s(" MODULE_NAME ")", service); va_start(args, format); /* other PAM modules seem to use LOG_AUTH, which the man page * marked as deprecated, so we use LOG_AUTHPRIV instead */ openlog(logname, LOG_CONS | LOG_PID, LOG_AUTHPRIV); vsyslog(priority, format, args); /* from BSD, not POSIX */ va_end(args); closelog(); } /* * Wrapper around conversation function (a callback function provided by * the PAM application to interact with the user) * * (based on converse in pam_unix/support.c) */ static int converse(pam_handle_t *pamh, int nargs, struct pam_message **message, struct pam_response **response, int debug) { int retval; struct pam_conv *conv; /* get pointer to conversation function */ retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv); if (retval != PAM_SUCCESS) { log_message(LOG_ERR, pamh, "no conversation function: %s", pam_strerror(pamh, retval)); return retval; } D(log_message(LOG_DEBUG, pamh, "calling conversation function")); /* call conversation function */ retval = conv->conv(nargs, (const struct pam_message **) message, response, conv->appdata_ptr); D(log_message(LOG_DEBUG, pamh, "conversation function returned %d", retval)); if (retval != PAM_SUCCESS) { log_message(LOG_WARNING, pamh, "conversation function failed: %s", pam_strerror(pamh, retval)); } return retval; /* propagate error status */ } /* we register cleanup() to be called when the app calls pam_end(), * to make sure that otpw_verify() gets a chance to remove locks */ static void cleanup(pam_handle_t *pamh, void *data, int err) { int debug = ((struct challenge *) data)->flags & OTPW_DEBUG; D(log_message(LOG_DEBUG, pamh,"cleanup() called, data=%p, err=%d", data, err)); if (((struct challenge *) data)->passwords) otpw_verify((struct challenge *) data, "entryaborted"); free(data); } /* * Issue password prompt with challenge and receive response from user * * (based on _set_auth_tok from pam_pwdfile.c, originally based * on pam_unix/support.c but that no longer seems to exist) */ static int get_response(pam_handle_t *pamh, char *challenge, int debug) { int retval; volatile char *p; struct pam_message msg, *pmsg[1]; struct pam_response *resp; char message[81]; /* format password prompt */ snprintf(message, sizeof(message), "Password %s: ", challenge); /* set up conversation call */ pmsg[0] = &msg; msg.msg_style = PAM_PROMPT_ECHO_OFF; msg.msg = message; resp = NULL; /* call conversation function */ if ((retval = converse(pamh, 1, pmsg, &resp, debug)) != PAM_SUCCESS) { /* converse has already output a warning log message here */ return retval; } /* error handling (just to be safe) */ if (!resp) { log_message(LOG_WARNING, pamh, "get_response(): resp==NULL"); return PAM_CONV_ERR; } if (!resp[0].resp) { log_message(LOG_WARNING, pamh, "get_response(): resp[0].resp==NULL"); free(resp); return PAM_CONV_ERR; } /* store response as PAM item */ pam_set_item(pamh, PAM_AUTHTOK, resp[0].resp); /* sanitize and free buffer */ for (p = resp[0].resp; *p; p++) *p = 0; free(resp[0].resp); free(resp); return PAM_SUCCESS; } /* * Display a notice (err==0) or error message (err==1) to the user */ static int display_notice(pam_handle_t *pamh, int err, int debug, char *format, ...) { int retval; struct pam_message msg, *pmsg[1]; struct pam_response *resp; char message[1024]; va_list args; /* format message */ va_start(args, format); vsnprintf(message, sizeof(message), format, args); va_end(args); /* set up conversation call */ pmsg[0] = &msg; msg.msg_style = err ? PAM_ERROR_MSG : PAM_TEXT_INFO /* PAM_TEXT_INFO */; msg.msg = message; resp = NULL; /* call conversation function */ if ((retval = converse(pamh, 1, pmsg, &resp, debug)) != PAM_SUCCESS) { /* converse has already output a warning log message here */ return retval; } /* memory wants to be free */ if (resp) { if (resp[0].resp) free(resp[0].resp); free(resp); } return PAM_SUCCESS; } /* provided entry point for auth service */ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { int retval; const char *username; char *password; struct passwd *pwd; struct challenge *ch = NULL; int i, debug = 0, otpw_flags = 0; /* parse option flags */ for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "debug")) { debug = 1; otpw_flags |= OTPW_DEBUG; } else if (!strcmp(argv[i], "nolock")) { otpw_flags |= OTPW_NOLOCK; } } D(log_message(LOG_DEBUG, pamh, "pam_sm_authenticate called, flags=%d", flags)); /* get user name */ retval = pam_get_user(pamh, &username, "login: "); if (retval == PAM_CONV_AGAIN) return PAM_INCOMPLETE; else if (retval != PAM_SUCCESS) { log_message(LOG_NOTICE, pamh, "no username provided"); return PAM_USER_UNKNOWN; } /* DEBUG */ D(log_message(LOG_DEBUG, pamh, "username is %s", username)); D(log_message(LOG_DEBUG, pamh, "uid=%d, euid=%d, gid=%d, egid=%d", getuid(), geteuid(), getgid(), getegid())); /* consult POSIX password database (to find homedir, etc.) */ pwd = getpwnam(username); if (!pwd) { log_message(LOG_NOTICE, pamh, "username not found"); return PAM_USER_UNKNOWN; } /* * Make sure that otpw_verify() is always called to clean up locks, * even if the connection is aborted while we are in get_response() * or something else goes wrong. */ ch = calloc(1, sizeof(struct challenge)); if (!ch) return PAM_AUTHINFO_UNAVAIL; retval = pam_set_data(pamh, MODULE_NAME":ch", ch, cleanup); if (retval != PAM_SUCCESS) { log_message(LOG_ERR, pamh, "pam_set_data() failed"); return PAM_AUTHINFO_UNAVAIL; } /* prepare OTPW challenge */ otpw_prepare(ch, pwd, otpw_flags); D(log_message(LOG_DEBUG, pamh, "challenge: %s", ch->challenge)); if (ch->passwords < 1) { /* it seems OTPW might not have been set up or has exhausted keys, perhaps explain here in info msg how to "man otpw-gen" */ log_message(LOG_NOTICE, pamh, "OTPW not set up for user %s", username); return PAM_AUTHINFO_UNAVAIL; } /* Issue challenge, get response */ retval = get_response(pamh, ch->challenge, debug); if (retval != PAM_SUCCESS) { log_message(LOG_ERR, pamh,"get_response() failed: %s", pam_strerror(pamh, retval)); return PAM_AUTHINFO_UNAVAIL; } retval = pam_get_item(pamh, PAM_AUTHTOK, (void *)&password); if (retval != PAM_SUCCESS) { log_message(LOG_ERR, pamh, "auth token not found"); return PAM_AUTHINFO_UNAVAIL; } if (!password) { /* NULL passwords are checked in get_response so this * point in the code should never be reached */ log_message(LOG_ERR, pamh, "password==NULL (should never happen)"); return PAM_AUTHINFO_UNAVAIL; } /* verify response */ retval = otpw_verify(ch, password); if (retval == OTPW_OK) { D(log_message(LOG_DEBUG, pamh, "password matches")); return PAM_SUCCESS; } else if (retval == OTPW_WRONG) { log_message(LOG_NOTICE, pamh, "incorrect password from user %s", username); return PAM_AUTH_ERR; } log_message(LOG_ERR, pamh, "OTPW error %d for user %s", retval, username); return PAM_AUTHINFO_UNAVAIL; } /* another expected entry point */ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { (void) pamh; (void) flags; (void) argc; (void) argv; /* NOP */ return PAM_SUCCESS; } /* this is called after the user has logged in */ PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { struct challenge *ch = NULL; int retval; int i, debug = 0; /* parse option flags */ for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "debug")) debug = 1; } D(log_message(LOG_DEBUG, pamh, "pam_sm_open_session called, flags=%d", flags)); retval = pam_get_data(pamh, MODULE_NAME":ch", (const void **) &ch); if (retval != PAM_SUCCESS || !ch) { log_message(LOG_ERR, pamh, "pam_get_data() failed"); return PAM_SESSION_ERR; } if (!(flags & PAM_SILENT) && ch->entries >= 0) { display_notice(pamh, 0, debug, "Remaining one-time passwords: %d of %d%s", ch->remaining, ch->entries, (ch->remaining < ch->entries/2) || (ch->remaining < 20) ? " (time to print new ones with otpw-gen)" : ""); } return PAM_SUCCESS; } /* another expected entry point */ PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { (void) pamh; (void) flags; (void) argc; (void) argv; /* NOP */ return PAM_SUCCESS; } #ifdef PAM_STATIC struct pam_module _pam_listfile_modstruct = { MODULE_NAME, pam_sm_authenticate, pam_sm_setcred, NULL, NULL, NULL, NULL }; #endif otpw/README0100644000307500030750000000061207724666126011407 0ustar mgk25mgk25 OTPW - A one-time password login package ---------------------------------------- OTPW is a one-time password authentication library and PAM module that provides a number of advantages over other systems. Please read the documentation in the file otpw.html for details. -- Markus Kuhn, Computer Laboratory, University of Cambridge http://www.cl.cam.ac.uk/~mgk25/ || CB3 0FD, Great Britain otpw/rmd160.c0100644000307500030750000002177307724666142011715 0ustar mgk25mgk25/********************************************************************\ * * FILE: rmd160.c * * CONTENTS: A sample C-implementation of the RIPEMD-160 * hash-function. * TARGET: any computer with an ANSI C compiler * * AUTHOR: Antoon Bosselaers, ESAT-COSIC * DATE: 1 March 1996 * VERSION: 1.0 * * Copyright (c) Katholieke Universiteit Leuven * 1996, All Rights Reserved * \********************************************************************/ /* header files */ #include "rmd160.h" /********************************************************************/ void rmd160_init(dword *MDbuf) { MDbuf[0] = 0x67452301UL; MDbuf[1] = 0xefcdab89UL; MDbuf[2] = 0x98badcfeUL; MDbuf[3] = 0x10325476UL; MDbuf[4] = 0xc3d2e1f0UL; return; } /********************************************************************/ void rmd160_compress(dword *MDbuf, dword *X) { dword aa = MDbuf[0], bb = MDbuf[1], cc = MDbuf[2], dd = MDbuf[3], ee = MDbuf[4]; dword aaa = MDbuf[0], bbb = MDbuf[1], ccc = MDbuf[2], ddd = MDbuf[3], eee = MDbuf[4]; /* round 1 */ FF(aa, bb, cc, dd, ee, X[ 0], 11); FF(ee, aa, bb, cc, dd, X[ 1], 14); FF(dd, ee, aa, bb, cc, X[ 2], 15); FF(cc, dd, ee, aa, bb, X[ 3], 12); FF(bb, cc, dd, ee, aa, X[ 4], 5); FF(aa, bb, cc, dd, ee, X[ 5], 8); FF(ee, aa, bb, cc, dd, X[ 6], 7); FF(dd, ee, aa, bb, cc, X[ 7], 9); FF(cc, dd, ee, aa, bb, X[ 8], 11); FF(bb, cc, dd, ee, aa, X[ 9], 13); FF(aa, bb, cc, dd, ee, X[10], 14); FF(ee, aa, bb, cc, dd, X[11], 15); FF(dd, ee, aa, bb, cc, X[12], 6); FF(cc, dd, ee, aa, bb, X[13], 7); FF(bb, cc, dd, ee, aa, X[14], 9); FF(aa, bb, cc, dd, ee, X[15], 8); /* round 2 */ GG(ee, aa, bb, cc, dd, X[ 7], 7); GG(dd, ee, aa, bb, cc, X[ 4], 6); GG(cc, dd, ee, aa, bb, X[13], 8); GG(bb, cc, dd, ee, aa, X[ 1], 13); GG(aa, bb, cc, dd, ee, X[10], 11); GG(ee, aa, bb, cc, dd, X[ 6], 9); GG(dd, ee, aa, bb, cc, X[15], 7); GG(cc, dd, ee, aa, bb, X[ 3], 15); GG(bb, cc, dd, ee, aa, X[12], 7); GG(aa, bb, cc, dd, ee, X[ 0], 12); GG(ee, aa, bb, cc, dd, X[ 9], 15); GG(dd, ee, aa, bb, cc, X[ 5], 9); GG(cc, dd, ee, aa, bb, X[ 2], 11); GG(bb, cc, dd, ee, aa, X[14], 7); GG(aa, bb, cc, dd, ee, X[11], 13); GG(ee, aa, bb, cc, dd, X[ 8], 12); /* round 3 */ HH(dd, ee, aa, bb, cc, X[ 3], 11); HH(cc, dd, ee, aa, bb, X[10], 13); HH(bb, cc, dd, ee, aa, X[14], 6); HH(aa, bb, cc, dd, ee, X[ 4], 7); HH(ee, aa, bb, cc, dd, X[ 9], 14); HH(dd, ee, aa, bb, cc, X[15], 9); HH(cc, dd, ee, aa, bb, X[ 8], 13); HH(bb, cc, dd, ee, aa, X[ 1], 15); HH(aa, bb, cc, dd, ee, X[ 2], 14); HH(ee, aa, bb, cc, dd, X[ 7], 8); HH(dd, ee, aa, bb, cc, X[ 0], 13); HH(cc, dd, ee, aa, bb, X[ 6], 6); HH(bb, cc, dd, ee, aa, X[13], 5); HH(aa, bb, cc, dd, ee, X[11], 12); HH(ee, aa, bb, cc, dd, X[ 5], 7); HH(dd, ee, aa, bb, cc, X[12], 5); /* round 4 */ II(cc, dd, ee, aa, bb, X[ 1], 11); II(bb, cc, dd, ee, aa, X[ 9], 12); II(aa, bb, cc, dd, ee, X[11], 14); II(ee, aa, bb, cc, dd, X[10], 15); II(dd, ee, aa, bb, cc, X[ 0], 14); II(cc, dd, ee, aa, bb, X[ 8], 15); II(bb, cc, dd, ee, aa, X[12], 9); II(aa, bb, cc, dd, ee, X[ 4], 8); II(ee, aa, bb, cc, dd, X[13], 9); II(dd, ee, aa, bb, cc, X[ 3], 14); II(cc, dd, ee, aa, bb, X[ 7], 5); II(bb, cc, dd, ee, aa, X[15], 6); II(aa, bb, cc, dd, ee, X[14], 8); II(ee, aa, bb, cc, dd, X[ 5], 6); II(dd, ee, aa, bb, cc, X[ 6], 5); II(cc, dd, ee, aa, bb, X[ 2], 12); /* round 5 */ JJ(bb, cc, dd, ee, aa, X[ 4], 9); JJ(aa, bb, cc, dd, ee, X[ 0], 15); JJ(ee, aa, bb, cc, dd, X[ 5], 5); JJ(dd, ee, aa, bb, cc, X[ 9], 11); JJ(cc, dd, ee, aa, bb, X[ 7], 6); JJ(bb, cc, dd, ee, aa, X[12], 8); JJ(aa, bb, cc, dd, ee, X[ 2], 13); JJ(ee, aa, bb, cc, dd, X[10], 12); JJ(dd, ee, aa, bb, cc, X[14], 5); JJ(cc, dd, ee, aa, bb, X[ 1], 12); JJ(bb, cc, dd, ee, aa, X[ 3], 13); JJ(aa, bb, cc, dd, ee, X[ 8], 14); JJ(ee, aa, bb, cc, dd, X[11], 11); JJ(dd, ee, aa, bb, cc, X[ 6], 8); JJ(cc, dd, ee, aa, bb, X[15], 5); JJ(bb, cc, dd, ee, aa, X[13], 6); /* parallel round 1 */ JJJ(aaa, bbb, ccc, ddd, eee, X[ 5], 8); JJJ(eee, aaa, bbb, ccc, ddd, X[14], 9); JJJ(ddd, eee, aaa, bbb, ccc, X[ 7], 9); JJJ(ccc, ddd, eee, aaa, bbb, X[ 0], 11); JJJ(bbb, ccc, ddd, eee, aaa, X[ 9], 13); JJJ(aaa, bbb, ccc, ddd, eee, X[ 2], 15); JJJ(eee, aaa, bbb, ccc, ddd, X[11], 15); JJJ(ddd, eee, aaa, bbb, ccc, X[ 4], 5); JJJ(ccc, ddd, eee, aaa, bbb, X[13], 7); JJJ(bbb, ccc, ddd, eee, aaa, X[ 6], 7); JJJ(aaa, bbb, ccc, ddd, eee, X[15], 8); JJJ(eee, aaa, bbb, ccc, ddd, X[ 8], 11); JJJ(ddd, eee, aaa, bbb, ccc, X[ 1], 14); JJJ(ccc, ddd, eee, aaa, bbb, X[10], 14); JJJ(bbb, ccc, ddd, eee, aaa, X[ 3], 12); JJJ(aaa, bbb, ccc, ddd, eee, X[12], 6); /* parallel round 2 */ III(eee, aaa, bbb, ccc, ddd, X[ 6], 9); III(ddd, eee, aaa, bbb, ccc, X[11], 13); III(ccc, ddd, eee, aaa, bbb, X[ 3], 15); III(bbb, ccc, ddd, eee, aaa, X[ 7], 7); III(aaa, bbb, ccc, ddd, eee, X[ 0], 12); III(eee, aaa, bbb, ccc, ddd, X[13], 8); III(ddd, eee, aaa, bbb, ccc, X[ 5], 9); III(ccc, ddd, eee, aaa, bbb, X[10], 11); III(bbb, ccc, ddd, eee, aaa, X[14], 7); III(aaa, bbb, ccc, ddd, eee, X[15], 7); III(eee, aaa, bbb, ccc, ddd, X[ 8], 12); III(ddd, eee, aaa, bbb, ccc, X[12], 7); III(ccc, ddd, eee, aaa, bbb, X[ 4], 6); III(bbb, ccc, ddd, eee, aaa, X[ 9], 15); III(aaa, bbb, ccc, ddd, eee, X[ 1], 13); III(eee, aaa, bbb, ccc, ddd, X[ 2], 11); /* parallel round 3 */ HHH(ddd, eee, aaa, bbb, ccc, X[15], 9); HHH(ccc, ddd, eee, aaa, bbb, X[ 5], 7); HHH(bbb, ccc, ddd, eee, aaa, X[ 1], 15); HHH(aaa, bbb, ccc, ddd, eee, X[ 3], 11); HHH(eee, aaa, bbb, ccc, ddd, X[ 7], 8); HHH(ddd, eee, aaa, bbb, ccc, X[14], 6); HHH(ccc, ddd, eee, aaa, bbb, X[ 6], 6); HHH(bbb, ccc, ddd, eee, aaa, X[ 9], 14); HHH(aaa, bbb, ccc, ddd, eee, X[11], 12); HHH(eee, aaa, bbb, ccc, ddd, X[ 8], 13); HHH(ddd, eee, aaa, bbb, ccc, X[12], 5); HHH(ccc, ddd, eee, aaa, bbb, X[ 2], 14); HHH(bbb, ccc, ddd, eee, aaa, X[10], 13); HHH(aaa, bbb, ccc, ddd, eee, X[ 0], 13); HHH(eee, aaa, bbb, ccc, ddd, X[ 4], 7); HHH(ddd, eee, aaa, bbb, ccc, X[13], 5); /* parallel round 4 */ GGG(ccc, ddd, eee, aaa, bbb, X[ 8], 15); GGG(bbb, ccc, ddd, eee, aaa, X[ 6], 5); GGG(aaa, bbb, ccc, ddd, eee, X[ 4], 8); GGG(eee, aaa, bbb, ccc, ddd, X[ 1], 11); GGG(ddd, eee, aaa, bbb, ccc, X[ 3], 14); GGG(ccc, ddd, eee, aaa, bbb, X[11], 14); GGG(bbb, ccc, ddd, eee, aaa, X[15], 6); GGG(aaa, bbb, ccc, ddd, eee, X[ 0], 14); GGG(eee, aaa, bbb, ccc, ddd, X[ 5], 6); GGG(ddd, eee, aaa, bbb, ccc, X[12], 9); GGG(ccc, ddd, eee, aaa, bbb, X[ 2], 12); GGG(bbb, ccc, ddd, eee, aaa, X[13], 9); GGG(aaa, bbb, ccc, ddd, eee, X[ 9], 12); GGG(eee, aaa, bbb, ccc, ddd, X[ 7], 5); GGG(ddd, eee, aaa, bbb, ccc, X[10], 15); GGG(ccc, ddd, eee, aaa, bbb, X[14], 8); /* parallel round 5 */ FFF(bbb, ccc, ddd, eee, aaa, X[12] , 8); FFF(aaa, bbb, ccc, ddd, eee, X[15] , 5); FFF(eee, aaa, bbb, ccc, ddd, X[10] , 12); FFF(ddd, eee, aaa, bbb, ccc, X[ 4] , 9); FFF(ccc, ddd, eee, aaa, bbb, X[ 1] , 12); FFF(bbb, ccc, ddd, eee, aaa, X[ 5] , 5); FFF(aaa, bbb, ccc, ddd, eee, X[ 8] , 14); FFF(eee, aaa, bbb, ccc, ddd, X[ 7] , 6); FFF(ddd, eee, aaa, bbb, ccc, X[ 6] , 8); FFF(ccc, ddd, eee, aaa, bbb, X[ 2] , 13); FFF(bbb, ccc, ddd, eee, aaa, X[13] , 6); FFF(aaa, bbb, ccc, ddd, eee, X[14] , 5); FFF(eee, aaa, bbb, ccc, ddd, X[ 0] , 15); FFF(ddd, eee, aaa, bbb, ccc, X[ 3] , 13); FFF(ccc, ddd, eee, aaa, bbb, X[ 9] , 11); FFF(bbb, ccc, ddd, eee, aaa, X[11] , 11); /* combine results */ ddd += cc + MDbuf[1]; /* final result for MDbuf[0] */ MDbuf[1] = MDbuf[2] + dd + eee; MDbuf[2] = MDbuf[3] + ee + aaa; MDbuf[3] = MDbuf[4] + aa + bbb; MDbuf[4] = MDbuf[0] + bb + ccc; MDbuf[0] = ddd; return; } /********************************************************************/ void rmd160_finish(dword *MDbuf, byte *strptr, dword lswlen, dword mswlen) { dword i; /* counter */ dword X[16]; /* message words */ for (i=0; i<16; X[i++]=0); /* put bytes from strptr into X */ for (i=0; i<(lswlen&63); i++) { /* byte i goes into word X[i div 4] at pos. 8*(i mod 4) */ X[i>>2] ^= (dword) *strptr++ << (8 * (i&3)); } /* append the bit m_n == 1 */ X[(lswlen>>2)&15] ^= (dword)1 << (8*(lswlen&3) + 7); if ((lswlen & 63) > 55) { /* length goes to next block */ rmd160_compress(MDbuf, X); for (i=0; i<16; X[i++]=0); } /* append length in bits*/ X[14] = lswlen << 3; X[15] = (lswlen >> 29) | (mswlen << 3); rmd160_compress(MDbuf, X); return; } /************************ end of file rmd160.c **********************/ otpw/Makefile0100644000307500030750000000173407740300202012147 0ustar mgk25mgk25# # Makefile - One-time password login capability # # Markus Kuhn # # $Id: Makefile,v 1.10 2003-09-30 21:14:27+01 mgk25 Rel $ # VERSION=1.3 CC=gcc CFLAGS=-O -ggdb -W -Wall TARGETS=otpw-gen demologin pam_otpw.so all: $(TARGETS) otpw-gen: otpw-gen.o rmd160.o md.o $(CC) -o $@ $+ demologin: demologin.o otpw.o rmd160.o md.o $(CC) -o $@ $+ -lcrypt otpw-gen.o: otpw-gen.c md.h conf.h otpw.o: otpw.c otpw.h md.h conf.h md.o: md.c md.h rmd160.h rmd160.o: rmd160.c rmd160.h otpw-l.o: otpw-l.c otpw.c otpw.h md.h conf.h pam_otpw.o: pam_otpw.c otpw.h md.h conf.h pam_otpw.so: pam_otpw.o otpw-l.o rmd160.o md.o ld --shared -o $@ $+ -lcrypt -lpam -lpam_misc ship: all clean ci -sRel -l RCS/* cd .. ; tar cvf otpw-$(VERSION).tar --exclude otpw/RCS otpw ; \ gzip -9 otpw-$(VERSION).tar mv ../otpw-$(VERSION).tar.gz ${HOME}/public_html/download/ cp otpw.html otpw.html~ mv otpw.html~ ${HOME}/public_html/otpw.html clean: rm -f $(TARGETS) *~ *.o core otpw/rmd160.h0100644000307500030750000001047207724666143011715 0ustar mgk25mgk25/********************************************************************\ * * FILE: rmd160.h * * CONTENTS: Header file for a sample C-implementation of the * RIPEMD-160 hash-function. * TARGET: any computer with an ANSI C compiler * * AUTHOR: Antoon Bosselaers, ESAT-COSIC * DATE: 1 March 1996 * VERSION: 1.0 * * Copyright (c) Katholieke Universiteit Leuven * 1996, All Rights Reserved * \********************************************************************/ #ifndef RMD160H /* make sure this file is read only once */ #define RMD160H #include /********************************************************************/ /* typedef 8, 16 and 32 bit types, resp. */ /* adapt these, if necessary, for your operating system and compiler */ typedef unsigned char byte; /* unsigned 8-bit integer */ typedef unsigned short word; /* unsigned 16-bit integer */ #if ULONG_MAX == 4294967295U typedef unsigned long dword; /* unsigned 32-bit integer */ #elif UINT_MAX == 4294967295U typedef unsigned int dword; /* unsigned 32-bit integer */ #endif /********************************************************************/ /* macro definitions */ /* collect four bytes into one word: */ #define BYTES_TO_DWORD(strptr) \ (((dword) *((strptr)+3) << 24) | \ ((dword) *((strptr)+2) << 16) | \ ((dword) *((strptr)+1) << 8) | \ ((dword) *(strptr))) /* ROL(x, n) cyclically rotates x over n bits to the left */ /* x must be of an unsigned 32 bits type and 0 <= n < 32. */ #define ROL(x, n) (((x) << (n)) | ((x) >> (32-(n)))) /* the three basic functions F(), G() and H() */ #define F(x, y, z) ((x) ^ (y) ^ (z)) #define G(x, y, z) (((x) & (y)) | (~(x) & (z))) #define H(x, y, z) (((x) | ~(y)) ^ (z)) #define I(x, y, z) (((x) & (z)) | ((y) & ~(z))) #define J(x, y, z) ((x) ^ ((y) | ~(z))) /* the eight basic operations FF() through III() */ #define FF(a, b, c, d, e, x, s) {\ (a) += F((b), (c), (d)) + (x);\ (a) = ROL((a), (s)) + (e);\ (c) = ROL((c), 10);\ } #define GG(a, b, c, d, e, x, s) {\ (a) += G((b), (c), (d)) + (x) + 0x5a827999UL;\ (a) = ROL((a), (s)) + (e);\ (c) = ROL((c), 10);\ } #define HH(a, b, c, d, e, x, s) {\ (a) += H((b), (c), (d)) + (x) + 0x6ed9eba1UL;\ (a) = ROL((a), (s)) + (e);\ (c) = ROL((c), 10);\ } #define II(a, b, c, d, e, x, s) {\ (a) += I((b), (c), (d)) + (x) + 0x8f1bbcdcUL;\ (a) = ROL((a), (s)) + (e);\ (c) = ROL((c), 10);\ } #define JJ(a, b, c, d, e, x, s) {\ (a) += J((b), (c), (d)) + (x) + 0xa953fd4eUL;\ (a) = ROL((a), (s)) + (e);\ (c) = ROL((c), 10);\ } #define FFF(a, b, c, d, e, x, s) {\ (a) += F((b), (c), (d)) + (x);\ (a) = ROL((a), (s)) + (e);\ (c) = ROL((c), 10);\ } #define GGG(a, b, c, d, e, x, s) {\ (a) += G((b), (c), (d)) + (x) + 0x7a6d76e9UL;\ (a) = ROL((a), (s)) + (e);\ (c) = ROL((c), 10);\ } #define HHH(a, b, c, d, e, x, s) {\ (a) += H((b), (c), (d)) + (x) + 0x6d703ef3UL;\ (a) = ROL((a), (s)) + (e);\ (c) = ROL((c), 10);\ } #define III(a, b, c, d, e, x, s) {\ (a) += I((b), (c), (d)) + (x) + 0x5c4dd124UL;\ (a) = ROL((a), (s)) + (e);\ (c) = ROL((c), 10);\ } #define JJJ(a, b, c, d, e, x, s) {\ (a) += J((b), (c), (d)) + (x) + 0x50a28be6UL;\ (a) = ROL((a), (s)) + (e);\ (c) = ROL((c), 10);\ } /********************************************************************/ /* function prototypes */ void rmd160_init(dword *MDbuf); /* * initializes MDbuffer to "magic constants" */ void rmd160_compress(dword *MDbuf, dword *X); /* * the compression function. * transforms MDbuf using message bytes X[0] through X[15] */ void rmd160_finish(dword *MDbuf, byte *strptr, dword lswlen, dword mswlen); /* * puts bytes from strptr into X and pad out; appends length * and finally, compresses the last block(s) * note: length in bits == 8 * (lswlen + 2^32 mswlen). * note: there are (lswlen mod 64) bytes left in strptr. */ #endif /* RMD160H */ /*********************** end of file rmd160.h ***********************/ otpw/otpw.html0100644000307500030750000005615307740300205012376 0ustar mgk25mgk25 OTPW - a one-time password login package

OTPW – A one-time password login package

Markus Kuhn, Computer Laboratory, University of Cambridge

Latest release: Version 1.3, 2003-09-30

Abstract

The OTPW package consists of the one-time-password generator otpw-gen plus two verification routines otpw_prepare() and otpw_verify() that can easily be added to programs such as login or ftpd on POSIX systems. For platforms that support the Pluggable Authentication Method (PAM) interface, a suitable wrapper is included as well. Login software extended this way will allow reasonably secure user authentication over insecure network lines. The user carries a password list on paper. The scheme is designed to be robust against theft of the paper list and race-for-the-last-letter attacks. Cryptographic hash values of the one-time passwords are stored for verification in the user's home directory.

Introduction

A well-known classic vulnerability of the Internet application protocol suite is the frequent cleartext transfer of passwords in the telnet, rsh, and ftp protocols. Modern replacements for these protocols such as Tatu Ylönen's Secure Shell allow comfortable and secure remote sessions and file transfers over network connection that are not trusted to provide confidentiality.

However, traveling computer users often want to connect to their home system via untrusted terminals at conference hotels, other universities, and airports, where trusted encryption software is not available. Even Secure Shell does not protect against keyboard eavesdropping software on the untrusted terminal. A loss of confidentiality is often acceptable in these situations for the session content, but not for reusable login passwords. One-time-password schemes avoid the transmission of authentication secrets that are of any value after they have been used. This provides a reasonable level of protection against the widely encountered password sniffing attacks. The goal of a one-time-password login scheme is merely to provide a significant increase of security over the classic telnet/rlogin login procedure. It does not aim to protect from sophisticated active attacks such as session hijacking, host emulation, man-in-the-middle, etc. against which ssh and SSL based protocols should be used if this level of protection is required.

A widely known one-time-password scheme is S/KEY [Hal94, HM96]. OTPW is not compatible with and is not derived from either S/KEY or OPIE. It is a completely independent and different design, which I believe fulfils my functional and security requirements better.

How it works

One-time password authentication with the OTPW package is accomplished via a file .otpw located in the user's home directory. No state is kept in any system-wide files, therefore OTPW does not introduce any new setuid root programs. As long as a user does not have .otpw in her home directory, the one-time-password facility has not been activated for her.

A user who wants to setup the one-time-password capability just executes the otpw-gen program. The program will ask for a prefix password that the user has to select and memorize and it will then write to standard output a password list such as:

  OTPW list generated 2003-08-31 21:06 on trillium.cl.cam.ac.uk

  000 IZdB bqyH  006 rF62 k6zi  012 JCFe 6at3  018 uaYT azuu  024 Nt7n b=fQ
  001 yCSo /VQs  007 Phvb =6ZW  013 8Pm7 DbYJ  019 OdAk H62c  025 /pOm :ZEA
  002 mESf +nWK  008 J9fH iXrn  014 MAds 6TTS  020 Aj6W 9O4P  026 DhCc yrPY
  003 x4vX HXr2  009 DGPC amts  015 B=xZ waPx  021 MzUP Ahsc  027 UWTe G3Fh
  004 A5z9 japt  010 s6cr xwZ5  016 sCgq Da5Y  022 Q=XK 4I7w  028 xszW Ha9L
  005 YCcA k29/  011 inn6 Rsa/  017 m8za o/HB  023 umS= gYoU  029 +HmG Rr6P

              !!! REMEMBER: Enter the PREFIX PASSWORD first !!!

Normally the output of otpw-gen should be sent directly to the printer as in

  otpw-gen | lpr

or should be first formatted with an ASCII to PostScript converter where necessary.

Fetch the printed list immediately from the printer, fold it, and keep it with you. The list shows the machine name and the creation time to allow users to find the latest list for the right machine. It does not show the user's name, because nobody is supposed to have the list of anyone else, but printer drivers such as a2ps might add it. Only a single list is required for a set of networked machines on which the user has a common home directory.

By default, otpw-gen generates 60 lines of output. Use the command line options -h lines, -w columns, and -s pages to specify the length of the output. No more than 1000 passwords will be generated at a time.

Where one-time-password authentication is used, the password prompt will be followed by a 3-digit password number. Enter first the prefix password that was given to otpw-gen, followed directly (without hitting return between) by the password with the requested number from the printed password list:

  login: kuhn
  Password 019: geHeimOdAkH62c

In this example, geHeim was the prefix password. The spaces in the password list are just there to increase readability and can be dropped.

A clever attacker might observe the password being entered and might try to use the fact that computers can send data much faster than users can finish entering passwords. In the several hundred milliseconds that the user needs to press the return key after the last character, an attacker could on a parallel connection to the same machine send the code of the return key faster than the user.

To prevent such a race-for-the-last-key attack, any login attempt that is taking place concurrently with another attempt will require three one-time passwords to be entered:

  login: kuhn
  Password 022/000/004: geHeimQ=XK4I7wIZdBbqyHA5z9japt

This might look inconvenient at first, but remember that three passwords will only be requested when someone tries to login simultaneously, which in itself should already cause suspicion. The three requested passwords are randomly selected but they will never include the single password that was requested in the first of the concurrent login attempts. Only the first requested single password will be locked, not any of the requested triples. This way, the three-password method ensures that an attacker cannot disable the OTPW mechanism by locking all passwords. The triple challenge ensures that many ten thousand network connections would be necessary to perform a race attack on the same password triple, which is not practical. The OTPW package creates a symbolic link .otpw.lock in the user's home directory to lock the first requested password while its input is pending. If a system crash created a stale lock, it will be removed after 24 hours.

The .otpw file looks like

  OTPW1
  30 3 12 8
  ---------------
  ---------------
  023vf+Uvbg7AqjC
  025Mm+mJWbStzL5
  024aPEXvP3pgUYa
  008r3b5efMVT%IU
  002AQ63iP8ymMWq
  005:PcEr:LoieKO
  ---------------
  0135:Gw==tjv=rA
  020rQQ2C2SQP%EU
  02197Jh5deXd8ga
  016qN9qPHh4CNz6
  010BcqejWc+kI6i
  027hkkmBcMZYxgg
  029tkB6Wm5yS=F7
  003BpcMoM=YwTPY
  018f=4hAXWpwetr
  014jiOz/aMow83k
  0099gRB2+8QX8BC
  012QBhdmm7k=Hqs
  007g6zMK%Ryp6hZ
  015XsTSUVUYTJ+8
  017uxVFK6BikgpV
  ---------------
  0067t5Z8SIdz/6h
  026CN/JqBQJLKZE
  028V5hj+3wYkFHq
  011VooKgV4PAJRD
  0016AtPvgkVe+Rw

The first line identifies the file format. The second line shows the

  • number of originally generated password entries,
  • digits per password number (3),
  • characters per base-64 encoded hash value (12),
  • characters per one-time password.

Every subsequent line is a password number followed by the RIPEMD-160 hash value of the prefix password concatenated with one of the printed one-time passwords. Only the first 12*6=72 bits of the hash value encoded in the same modified base-64 encoding that was used to generate the passwords is stored there. As soon as a one-time password has been entered correctly, the corresponding .otpw line will be overwritten with hyphens to prevent any reuse of this password.

Installation

Get the OTPW package otpw-*.*.tar.gz from http://www.cl.cam.ac.uk/~mgk25/download/.

First have a look into conf.h where some system parameters can be changed. The only item there really worth being checked carefully is the ENTROPY_CMDS macro. It contains a list of shell commands, whose output is used to initialize the random number generator in otpw-gen. Make sure that at least most of these shell commands actually do work on your system and produce an output that is extremely difficult to predict for potential attackers. If your operating system has a /dev/random or /dev/urandom device that gives access to a good true random number generator, then make sure that a command reads and outputs about 20 bytes from there. In ENTROPY_ENV, some environment variables (e.g., PATH) can be set for these shell commands.

Just start make to compile the otpw-gen and demologin programs. While otpw-gen can directly be made available to the user, demologin is only an example of the modifications that you have to make to your login program to support one-time passwords using OTPW.

The login program has to call

  #include "otpw.h"

  struct challenge ch;
  struct passwd *pwd;

  otpw_prepare(&ch, pwd);

after the user name has been entered and the POSIX password database entry has been read into *pwd. The routine will select a challenge password, which will be stored in ch.challenge, and will lock it if necessary. If this was not possible, for instance because the user has no unused passwords left, then ch.challenge will be an empty string. Otherwise, ch.challenge should be displayed to the user in some way before the password prompt. After the password has been entered, call

  result = otpw_verify(&ch, password);

and if result == OTPW_OK then accept the user as authorized, otherwise do not. It is very important that otpw_verify() is always called after otpw_prepare() has returned successfully (i.e., with a non-empty ch.challenge string), as otherwise a stale lock might remain set. In otpw_prepare(), the current working directory will be changed to the user's home directory, and otpw_verify() expects that it is still there. If after otpw_verify() has returned, the condition ch.entries > 2 * ch.remaining is true and half of all passwords have been used, the user should be remembered to generate a new password list by executing otpw-gen. The security of OTPW is increased if a new password list is created long before all passwords on the old list have been used.

Both routines can be called with the root user id and they will temporarily change the effective user-id to that of the user in order to access the password file in the home directory. OTPW does not use any of the POSIX advisory file locking system calls, as those often do not work reliably over network file systems.

PAM installation

If your system supports Pluggable Authentication Modules [Mor01,XSSO], then simply compile the shared library pam_otpw.so and copy it to the directory in which other PAM modules reside (under Linux usually /lib/security/). Then edit the PAM configuration file for the application in which you want to use OTPW, as described in your PAM System Administrators' Guide. The pam_otpw.so provides both an authentication and a session component. The authentication component asks for and verifies a one-time password, the session component prints after each successful login a reminder on how many unused passwords you have left.

To use both components when loggin into your system via Secure Shell, you might have to add in /etc/pam.d/sshd the lines

  auth            required        pam_otpw.so
  session         optional        pam_otpw.so
The pam_otpw.so module ignores at present any options provided.

With OpenSSH 3.4 for example, you need to make sure that your version has PAM support compiled in, and you will have to add in /etc/ssh/sshd_config the lines

  UsePrivilegeSeparation          no
  PAMAuthenticationViaKbdInt      yes

To force OpenSSH to use PAM authentication (instead of its own hostbased or publickey methods, which it normally tries first), use "ssh -o PreferredAuthentications=keyboard-interactive".

Design rationale

Unlike S/KEY [Hal94, HM96], OTPW is not based on the Lamport scheme [Lam77] in which every one-time password is the one-way hash value of its successor. Password lists based on the Lamport scheme have the problem that if the attacker can see one of the last passwords on the list, then all previous passwords can be calculated from it. We also do not store the encrypted passwords as suggested by Rubin [Rub96], in order to keep the host free of files with secrets. Both proposals aimed to save memory in the host system. Instead, we store the one-way hash value of every single password in a potentially widely readable file in the user's home directory. For instance, hash values of 300 passwords (a typical A4 page) require only a four kilobyte long .otpw file, a typically negligible amount of storage space.

The passwords are carefully generated random numbers. The random number generator is based on the RIPEMD-160 secure hash function [DBP96, ISO03]. The random number generator is seeded by hashing together the output of various shell commands. These provide unpredictability in the form of a system random number seed, access times of important system files, usage history of the host, and more. The random state is the 160-bit output of the hash function. The random state is iterated after each use by concatenating the old state with the current high-resolution timer output and hashing the result again. The first 72 bits of the hash output are encoded with a modified base64 [FB96] scheme to produce readable passwords, while the remaining 88 bits represent the undisclosed internal state of the random number generator.

In many fonts, the characters 0O or 1lI are difficult to distinguish, therefore the modified base64 encoding replaces the three characters 01l by :=%. If for instance a zero is confused with a capital O by the user, the password verification routine will automatically correct for this.

S/KEY uses sequences of short English words as passwords. OTPW uses by default a base64 encoding instead, because that allows more passwords to be printed on a single page, with the same password entropy. In addition, an average human spy needs over 30 seconds to write a 12-character random string into short-term memory, which provides a good protection against brief looks that an attacker might have on a password list. Lists of short words on the other hand are much faster to memorize. OTPW can handle arbitrary password generation algorithms, as long as the length of the password is fixed. In the current version, otpw-gen can generate both base-64 encoded (option -p0) and 4-letter-word encoded (option -p1) passwords with a user-specified entropy (option -e).

The prefix password ensures that neither stealing the password list nor eavesdropping the line alone can provide unauthorized access. Admittedly, the security obtained by OTPW is not comparable with that of a challenge-response system in which the user has a PIN protected special calculator that generates the response. On the other hand, a piece of paper is much more portable, much more robust, and much cheaper than a special calculator. OTPW was designed for the large user base, for which an extra battery-powered device is inconvenient or not cost effective and who therefore still use normal Unix passwords everywhere.

In contrast to the suggestion made in RFC 1938, OTPW does not lock more than one one-time password at a time. If we did this, an attacker could easily exhaust our list of unlocked passwords and force us to either not login at all or use the normal Unix login password. Therefore, OTPW locks only one single password and for all further logins a triple-challenge is issued. If more than 100 unused passwords remain available, then there are over a million different challenges and an attacker has very little chance to perform a successful race attack while the authorized user finishes password entry.

The RIPEMD-160 routine was provided by Antoon Bosselaers (Katholieke Universiteit Leuven). Steven Murdoch wrote significant parts of the PAM wrapper and prepared a test setup. Also thanks to Kan Zhang, Piete Brooks and David Wheeler for useful suggestions.

Known problems

  • The OTPW PAM module exposes a bug in OpenSSH 3.6.1 (and possibly many other versions) under Linux:

    Observation: If the ssh user aborts the keyboard-interactive password entry, for example by pressing Ctrl-C, then OTPW will not remove its ~/.otpw.lock file. As a result, the user will get a triple-password request prompted at the next login attempt.

    Analysis: Debugging the sshd process with a breakpoint at the start of pam_end() shows that when ssh aborts the connection while sshd is inside the "conversation function" that sshd provides to the PAM system, then the conversation function never returns to PAM. Instead sshd calls pam_end() directly from inside the conversation function. This is in violation of The Linux-PAM application developers' guide (draft 0.73, 2000-12-02), which states in section 3.2.1, page 14 that "should an error occur the application should [...] simply return PAM_CONV_ERR". Linux-PAM keeps in pamh->caller_is track of whether the calling thread was supposed to come from the application (caller_is=2) or from the PAM module (caller_is=1). (See Linux-PAM-0.75/libpam/pam_private.h for the relevant macros.) The incorrect call of pam_end() simply results in an error message by Linux-PAM, because Linux-PAM thinks based on its pamh->caller_is=1 value that pam_end() was accidentally called by the module. As a result, none of the PAM data structures are cleaned up, one side effect of which is that the OTPW lock file remains in place. Sshd reacts on the failure of pam_end() with the syslog message "Cannot release PAM authentication[4]: System error". Another security-relevant side effect is that the memory scrubbing that PAM normally applies carefully to any password buffers never takes place!

    Workaround: None. This needs to be fixed urgently by the OpenSSH developers, as it also breaks the proper deallocation of buffers with secrets.

License

OTPW – A one-time password login package

Copyright © 1998-2003 Markus Kuhn

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

References

[Lam77]   Leslie Lamport: Password Authentication with Insecure Communication, Communications of the ACM, Vol 24, No 11, November 1981, pp 770-772

[Hal94]   Neil Haller, The S/KEY One-Time Password System, Proceedings of the ISOC Symposium on Network and Distributed System Security, February 1994, San Diego, CA.

[HM96]   Neil Haller, Craig Metz: A One-Time Password System, RFC 1938, May 1996.

[Rub96]   Aviel D. Rubin: Independent One-Time Passwords, USENIX Journal of Computer Systems, February, 1996.

[DBP96]   H. Dobbertin, A. Bosselaers, B. Preneel: RIPEMD-160, a strengthened version of RIPEMD, Fast Software Encryption, LNCS 1039, D. Gollmann, Ed., Springer-Verlag, 1996, pp. 71-82.

[ISO03]   ISO/IEC 10118-3, Information technology - Security techniques - Hash-functions - Part 3: Dedicated hash-functions, International Organization for Standardization, Geneva, 2003.

[FB96]   Ned Freed, Nathaniel S. Borenstein: Multipurpose Internet Mail Extensions (MIME) - Part One: Format of Internet Message Bodies, RFC 2045, November 1996.

[Mor01]   Andrew G. Morgan: Linux-PAM.

[XSSO]   X/Open Single Sign-On Service (XSSO) - Pluggable Authentication. June 1997.

Markus Kuhn
created 1997-11-25 -- last modified 2003-09-30 -- http://www.cl.cam.ac.uk/~mgk25/otpw.html