pax_global_header00006660000000000000000000000064123707506530014522gustar00rootroot0000000000000052 comment=bcd82c7be081f8eabba4bef3a6646d6d9759781b otpw-1.5/000077500000000000000000000000001237075065300123605ustar00rootroot00000000000000otpw-1.5/CHANGES000066400000000000000000000042431237075065300133560ustar00rootroot00000000000000 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 Changes in version 1.4 (2004-03-??) - removed upper limit for entropy, i.e. passwords can now be practically arbitrarily long (option -e) - new option -o to disable the random permutation of passwords in the ~/.otpw file, such that they are requested in the order printed on the paper - new option -n to suppress the 4-line header and footer on each printed page - at least one column of passwords will always be printed, even with option -w 0 - new option -r suggests one random password to stdout, then exists - new password format that uses only lowercase letters and digits (-p 3), means to be easy to communicate via voice - new option -m to generate passwords from a single randomly generated key, which will be printed to stderr - new option -k to recreate a password list from a master key generated with -m (this does not affect the ~/.otpw file) - new options -E and -P to control the entropy and form of the master key generated with -m Changes in version 1.5 (2014-08-07) - support for pseudo-user installation: if system-user “otpw” exists and otpw-gen is SETUID and owned by “otpw”, the hash file will be called ~otpw/john instead of ~john/.otpw, and be out of reach of the user - otpw-gen: new option -l to remove lock symlink (useful if the lock is owned by “otpw” rather than the user) - ABI of otpw.c/otpw.h changed to allow for better run-time configuration - minor cleanup, mainly to reduce warnings of modern compilers otpw-1.5/Makefile000066400000000000000000000037141237075065300140250ustar00rootroot00000000000000# # Makefile - One-time password login system # # Markus Kuhn # VERSION=1.5 CC=gcc CFLAGS=-O -ggdb -W -Wall -Wno-unused-result -fPIC %.gz: % gzip -9c $< >$@ TARGETS=otpw-gen demologin pam_otpw.so pam_otpw.8.gz otpw-gen.1.gz all: $(TARGETS) otpw-gen: otpw-gen.o rmd160.o md.o otpw.o $(CC) -o $@ $+ demologin: demologin.o otpw.o rmd160.o md.o $(CC) -o $@ $+ -lcrypt otpw-gen.o: otpw-gen.c md.h otpw.h otpw.o: otpw.c otpw.h md.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 pam_otpw.o: pam_otpw.c otpw.h md.h pam_otpw.so: pam_otpw.o otpw-l.o rmd160.o md.o ld --shared -o $@ $+ -lcrypt -lpam -lpam_misc distribution: git archive --prefix otpw-$(VERSION)/ -o otpw-$(VERSION).tar.gz v$(VERSION) release: git diff --exit-code v$(VERSION) -- otpw.html rsync -t otpw-$(VERSION).tar.gz $(HOME)/public_html/download/ rsync -t otpw.html $(HOME)/public_html/ #PAMLIB=/lib/security PAMLIB=/lib/x86_64-linux-gnu/security install: install-pam install-gen install-pam: pam_otpw.so pam_otpw.8.gz rsync -t pam_otpw.so $(PAMLIB)/ rsync -t pam_otpw.8.gz /usr/share/man/man8/ perl -i.bak -pe 's/^(\@include common-auth)$$/\# $$1\nauth required pam_otpw.so/' /etc/pam.d/sshd perl -i.bak -pe 's/^(ChallengeResponseAuthentication\s+)no$$/$$1yes/' \ /etc/ssh/sshd_config killall -SIGHUP sshd install-gen: otpw-gen otpw-gen.1.gz rsync -t otpw-gen /usr/bin/ rsync -t otpw-gen.1.gz /usr/share/man/man1/ -getent passwd otpw && \ chown otpw /usr/bin/otpw-gen && chmod u+s /usr/bin/otpw-gen install-pseudouser: adduser --system --gecos 'Pseudouser for storing one-time password files' --home /var/lib/otpw otpw uninstall-pseudouser: deluser --remove-home otpw uninstall: rm -f $(PAMLIB)/pam_otpw.so /usr/share/man/man8/pam_otpw.8.gz rm -f /usr/bin/otpw-gen /usr/share/man/man1/otpw-gen.1.gz clean: rm -f $(TARGETS) *~ *.o core test-login: ssh -o PreferredAuthentications=keyboard-interactive localhost otpw-1.5/README000066400000000000000000000006121237075065300132370ustar00rootroot00000000000000 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-1.5/TODO000066400000000000000000000026321237075065300130530ustar00rootroot00000000000000 - implement options use_first_pass and try_first_pass, like the kerb5 module does (pb) - check what other "standard" options every pam module should offer (pb) - option for otpw-gen to generate only one single password (pb) - global config file; reduce the number of compile-time options (pb) - move .otpw out of home directory, in order to - make it work if $HOME is not yet mounted (/var/otpw/$LOGNAME) (pb) - users can be prevented from recycling passwords (this would require otpw-gen to become setuid) (some U.S. nuclear lab) - add generation time and (optional) expiry time to .otpw file (pb) - what happens with the 3-password challenge if there is only a single password left? (pb) - make lock timeout (default 24 h) configurable (pb) - "buddy file" with list of other users who can add a one-time password - add GPL boilerplate more prominently - Compiling on openSUSE 10.3/x86_64 leads to: ld --shared -o pam_otpw.so pam_otpw.o otpw-l.o rmd160.o md.o \ -lcrypt -lpam -lpam_misc ld: pam_otpw.o: relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC pam_otpw.o: could not read symbols: Bad value - option for pam_otpw to restrict the passwords actually used to a subset (e.g., range, modulus), for the case where multiple hosts have copies of the same password file but must not ask for the same. otpw-1.5/demologin.c000066400000000000000000000071751237075065300145130ustar00rootroot00000000000000/* * Simple demonstration application that supports one-time passwords * * Markus Kuhn */ #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 otpw_pwdbuf *user; 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 */ otpw_getpwnam(username, &user); /* in one-time password mode, set lock and output challenge string */ if (use_otpw) { ch.challenge[0] = 0; if (user) otpw_prepare(&ch, &user->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 (user && spwd) user->pwd.pw_passwd = spwd->sp_pwdp; if (geteuid() != 0) fprintf(stderr, "Shadow password access requires root priviliges.\n"); #endif if (!user || strcmp(crypt(password, user->pwd.pw_passwd), user->pwd.pw_passwd)) printf("Login incorrect\n"); else printf("Login correct\n"); } return 0; } otpw-1.5/md.c000066400000000000000000000125531237075065300131320ustar00rootroot00000000000000/* * Universal wrapper API for a message digest function * * Markus Kuhn */ #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, const void *src, size_t len) { int i; int remaining = md->length_lo & (MD_BUFLEN-1); unsigned chunk; unsigned long old_lo = md->length_lo; dword X[16]; /* update 64-bit counter of bytes added so far */ md->length_lo += len & 0xffffffff; if (md->length_lo < old_lo) md->length_hi++; #if SIZE_MAX > 4294967295U md->length_hi += len >> 32; if (md->length_lo > 4294967295U) { md->length_hi += md->length_lo >> 32; md->length_lo &= 0xffffffff; } #endif /* complete remaining input block of compression function */ 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] = BYTES_TO_DWORD(md->buf + i); rmd160_compress((dword *) md->md, X); } } /* feed whole input blocks to compression function */ while (len >= MD_BUFLEN) { for (i = 0; i < 64; i += 4) X[i>>2] = BYTES_TO_DWORD((unsigned char *)src + i); rmd160_compress((dword *) md->md, X); src += MD_BUFLEN; len -= MD_BUFLEN; } /* partially fill buffer with remaining bytes */ 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-1.5/md.h000066400000000000000000000012721237075065300131330ustar00rootroot00000000000000/* * Universal wrapper API for a message digest function * * Markus Kuhn */ #ifndef MD_H #define MD_H #include #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, const void *src, size_t len); void md_close(md_state *md, unsigned char *result); int md_selftest(void); #endif otpw-1.5/otpw-gen.1000066400000000000000000000146631237075065300142140ustar00rootroot00000000000000.TH OTPW-GEN 1 "2014-08-07" .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 using option .BR -l . 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 attacker 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 \-p 0 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 \-p 1 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 \-p 2 Generate passwords by transforming a random bit string into a sequence of lowercase letters and digits (5 bits per character). These are easier to communicate by voice (e.g., using the NATO alphabet). .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. .TP .BI \-n Suppress the addition of a header and footer line to each output page. This reduces the minimum value for option .I \-h to 1. .TP .BI \-m Instead of generating each password randomly, generate a random .I master key and then derive each password from that in a deterministic way. The master key will be printed to standard error. It can later be used with option .I \-k to recreate another copy of the same one-time password list. (Each password is generated from the output of a secure hash function applied to the master key and the challenge string.) .TP .BI \-E " number" Specify the minimum entropy of the master key in bits. (It contains in addition four bits redundancy for error checking.) .TP .BI \-P " number" Choose the text format in which the master key will be displayed. The supported values are the same as with option .IR \-p . .TP .BI \-k Ask for a master key, as it was generated by option .IR -m , and then recreate the same password list from that. With this option, only a password list will be generated; the hash values in .I ~/.otpw remain unmodified. .TP .BI \-r Output a suggestion for a random password, then exit. The length and type of password can be selected with options .I \-e and .IR \-p . .TP .BI \-l Remove any lock file left by previous authentication attempts, then exit. .SH PSEUDO-USER INSTALLATION If the .B otpw-gen binary, owned by some system pseudo user (e.g., “otpw”), has the SETUID bit set, then the password hash file will be owned by and stored in the home directory of that pseudo user (e.g., “/var/lib/otpw”), using the user's name instead of “.otpw”. This way, the hash files are out of reach from the users, and cannot be manipulated by tools other than .BR otpw-gen , which can help to enforce policies about how passwords are generated. Storing the password hash files outside the user's home directory can also be useful where the home directory may not yet be accessible during login. .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-1.5/otpw-gen.c000066400000000000000000001211721237075065300142700ustar00rootroot00000000000000/* * One-time password generator * * Markus Kuhn */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "otpw.h" #define NL "\r\n" /* new line sequence in password list output */ #define FF "\f\n" /* form feed sequence in password list output */ #define MAX_PASSWORDS 1000 /* maximum length of password list */ #define MASTERKEY_CHECKBITS 4 /* error-detection bits in master key */ /* shell commands that provide high entropy output for RNG */ char *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 entropy_cmds */ char *entropy_env[] = { "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/etc:/usr/etc:/usr/ucb" }; /* Minimum password entropy [bits] permitted by otpw-gen (option -e) */ int emin=30; /* suffix added to temporary OTPW file */ char *tmpsuffix = ".tmp"; /* * A list of common English four letter words. It has not been checked * particularly well for being free of rude words or trademarks; users * are meant to 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, &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, &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) { 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, &entropy, sizeof(entropy)); md_close(&md, r); } /* Determine the next random bit generator state by hashing the * previous state along with the the time and some magic string */ void rbg_iter(unsigned char *r) { md_state md; struct timeval t; md_init(&md); gettimeofday(&t, NULL); md_add(&md, &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); } /* Generate a random byte string s with len bytes from a * seed string seed with length slen */ void random_string(const void *seed, size_t slen, void *s, size_t len) { md_state md; unsigned char r[MD_LEN]; size_t i; char j; assert(len <= 0xffffffff); md_init(&md); md_add(&md, seed, slen); md_close(&md, r); for (i = 0; i < len; i++) { *((unsigned char *)s++) = r[0]; md_init(&md); j = i >> 24; md_add(&md, &j, 1); j = i >> 16; md_add(&md, &j, 1); j = i >> 8; md_add(&md, &j, 1); j = i; md_add(&md, &j, 1); md_add(&md, r, MD_LEN); /* this lines does not add entropy */ md_add(&md, seed, slen); /* that is not already added here! */ 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'; } /* * Transform the first 5*chars bits of the binary string v into a chars * character long string s. The encoding uses only lowercase letters * and digit, in order to make it easy to communicate by voice (e.g., * using the NATO alphabet). */ void conv_base32(char *s, const unsigned char *v, int chars) { static const char tab[] = "abcdefghijkmnpqrstuvwxyz23456789"; int i, j = 0, k = 0; for (i = 0; i < chars; i++) { if (k < 5) { j = j << 8 | *(v++); k += 8; } *s++ = tab[(j >> (k-5)) & 31]; k -= 5; } *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 #define PW_BASE32 2 /* * Convert a random bit sequence into a printable password * * Input: vr random bit string * vlen length of vr in bytes * type 0: modified base-64 encoding * 1: sequence of 4-letter words * 2: base-32 encoding (lowercase plus digits) * 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 void *vr, 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; const unsigned char *v = vr; /* calculate length of output and actually used entropy */ switch (type) { case PW_BASE32: pwchars = (entropy + 4) / 5; entropy = pwchars * 5; pwlen = pwchars + (pwchars > 5 ? (pwchars - 1) / 4 : 0); emax = ((vlen * 8) / 5) * 5; break; 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_BASE32: case PW_BASE64: if (type == PW_BASE32) conv_base32(buf, v, pwchars); else 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) { unsigned char r[MD_LEN], h[MD_LEN]; md_state md; int i, j, k, l; struct otpw_pwdbuf *user = NULL, *pseudouser = NULL; FILE *f; char timestr[81], hostname[81], challenge[81]; char password1[1024], password2[1024]; char *password; char *masterkey, *normal_masterkey = NULL; int pwlen, pwchars, mklen; char header[LINE_MAX]; char *fnout = NULL; char *fntmp; struct termios term, term_old; int stdin_is_tty = 0; int width = 79, height = 60, pages = 1, rows; int header_lines = 4, random_order = 1; int entropy = 48, emax, type = PW_BASE64; int key_entropy = 76, key_type = PW_BASE32; int use_masterkey = 0, regenerate = 0, unlock = 0; int cols; time_t t; char *hbuf, *rndbuf; int rndbuflen; int challen = 3; /* number of characters in challenge */ int hbuflen = challen + otpw_hlen + 1; int help = 0; assert(md_selftest() == 0); assert(otpw_hlen * 6 < MD_LEN * 8); assert(otpw_hlen >= 8); /* read command line arguments */ for (i = 1; i < argc && !help; i++) { if (argv[i][0] == '-') for (j = 1; j > 0 && !help && argv[i][j] != 0; j++) switch (argv[i][j]) { case 'h': if (++i >= argc) { help = 1; break; } height = atoi(argv[i]); j = -1; break; case 'w': if (++i >= argc) { help = 1; break; }; width = atoi(argv[i]); j = -1; break; case 's': if (++i >= argc) { help = 1; break; } pages = atoi(argv[i]); if (pages < 1) { fprintf(stderr, "Specify at least 1 page after -s!\n"); exit(1); } j = -1; break; case 'e': if (++i >= argc || (entropy = atoi(argv[i])) < 1) { help = 1; break; } j = -1; break; case 'E': if (++i >= argc || (key_entropy = atoi(argv[i])) < 1) { help = 1; break; } j = -1; break; case 'p': if (strlen(argv[i]+j) == 2 && argv[i][j+1] >= '0' && argv[i][j+1] <= '1') { /* just for backwards compatibility with version 1.3 */ type = argv[i][j+1] - '0'; j = -1; break; } if (++i >= argc) { help = 1; break; } type = atoi(argv[i]); if (make_passwd(NULL, 0, type, 0, NULL, 0) == -1) { help = 1; break; } j = -1; break; case 'P': if (++i >= argc) { help = 1; break; } key_type = atoi(argv[i]); if (make_passwd(NULL, 0, key_type, 0, NULL, 0) == -1) { help = 1; break; } j = -1; break; case 'f': if (++i >= argc) { help = 1; break; } fnout = argv[i]; j = -1; break; case 'd': debug = 1; break; case 'n': header_lines = 0; break; case 'o': random_order = 0; break; case 'm': use_masterkey = 1; break; case 'k': regenerate = 1; break; case 'r': rbg_seed(r); rndbuflen = entropy / 8 + 16; rndbuf = malloc(rndbuflen); pwlen = make_passwd(rndbuf, rndbuflen, type, entropy, NULL, 0); /* pwlen */ assert(pwlen >= 0); password = malloc(pwlen+1); if (!rndbuf || !password) { fprintf(stderr, "Memory allocation error!\n"); exit(1); } random_string(r, MD_LEN, rndbuf, rndbuflen); assert(make_passwd(rndbuf, rndbuflen, type, entropy, NULL, 3) >= entropy); /* emax */ l = make_passwd(rndbuf, rndbuflen, type, entropy, password, pwlen+1); assert(l >= 0); printf("%s\n", password); rbg_iter(r); rbg_iter(r); /* memory scrubbing */ memset(password, 0xaa, pwlen); /* memory scrubbing */ exit(0); case 'l': unlock = 1; break; default: help = 1; } else { help = 1; } } if (fnout) { /* if an output file was specified, drop privileges */ if (getuid() != geteuid()) { if (setresuid(-1, getuid(), getuid()) || getuid() != geteuid()) { fprintf(stderr, "Dropping setuid privileges failed!\n"); exit(1); } } if (getgid() != getegid()) { if (setresgid(-1, getgid(), getgid()) || getgid() != getegid()) { fprintf(stderr, "Dropping setgid privileges failed!\n"); exit(1); } } } else { /* construct one-time password file path from relevant passwd entries */ otpw_getpwuid(getuid(), &user); if (!user) { fprintf(stderr, "Can't access your passwd database entry!\n"); exit(1); } if (getuid() != geteuid()) { /* we are setuid pseudouser */ otpw_getpwuid(geteuid(), &pseudouser); if (!pseudouser) { fprintf(stderr, "Can't access setuid pseudouser passwd entry!\n"); exit(1); } fnout = (char *) malloc(strlen(pseudouser->pwd.pw_dir) + 1 + strlen(user->pwd.pw_name) + 1); if (!fnout) abort(); strcpy(fnout, pseudouser->pwd.pw_dir); strcat(fnout, "/"); strcat(fnout, user->pwd.pw_name); } else { /* we are a normal user process */ fnout = (char *) malloc(strlen(user->pwd.pw_dir) + 1 + strlen(otpw_file) + 1); if (!fnout) abort(); strcpy(fnout, user->pwd.pw_dir); strcat(fnout, "/"); strcat(fnout, otpw_file); } } if (help) { /* Print brief usage instructions, then abort */ fprintf(stderr, "One-Time Password Generator v 1.5 -- Markus Kuhn\n\n"); fprintf(stderr, "%s [options]\n\n", argv[0]); fprintf (stderr, "Options: (default or current value in parenthesis)\n\n" " -h \tnumber of output lines (60)\n" " -w \tmax width of output lines (79)\n" " -s \tnumber of output pages (1)\n" " -e \tminimum entropy of each one-time password [bits]\n" "\t\t(low security: <30, default: 48, high security: >60)\n" " -p 0\t\tpasswords from modified base64 encoding (default)\n" " -p 1\t\tpasswords from English 4-letter words\n" " -p 2\t\tpasswords use only lowercase letters and digits\n" " -f \toutput hash file (%s)\n", fnout); fprintf (stderr, " -n\t\tdo not add header and footer lines to output\n" " -o\t\tuse passwords in printed order (default: random order)\n" " -m\t\tgenerate and display a master key for the password list\n" " -E \tminimum entropy of master key [bits] (76)\n" " -P \tencoding for master key (available values as for -p)\n" " -k\t\task for a master key and then regenerate a password\n\t\tlist" " from it (this won't change %s)\n", fnout); fprintf (stderr, " -r\t\tsuggest a random password, then exit\n" " -l\t\tremove lock file %s%s, then exit\n", fnout, otpw_locksuffix); fprintf (stderr, " -d\t\toutput debugging information\n" "\nTypical uses:\n\n" " otpw-gen | lpr\n\n" " Generate password list and print it. The hash values for each " "password\n will be stored in %s for use during login " "verification.\n\n" " otpw-gen -h 70 -s 2 | a2ps -1B -L 70 --borders no\n\n" " Generate password list and print it with nicer formatting.\n\n", fnout); exit(1); } if (unlock) { goto unlock; } /* check and process some parameters */ rows = height - header_lines; if (rows <= 0) { fprintf(stderr, "At least %d lines per page required (incl. header)!\n", header_lines + 1); exit(1); } if (width < 64 && header_lines) { fprintf(stderr, "Specify not less than 64 character " "wide lines!\n"); exit(1); } if (use_masterkey && regenerate) { fprintf(stderr, "Options -m and -k are mutually exclusive!\n"); exit(1); } /* check whether entropy is ok */ if (entropy < emin) { fprintf(stderr, "Entropy must be at least %d bits!\n", emin); exit(1); } /* check whether entropy is ok */ if (key_entropy < 60) { fprintf(stderr, "Masterkey entropy must be at least %d bits!\n", 60); exit(1); } /* allocate buffers for password generation */ rndbuflen = (entropy > key_entropy ? entropy : key_entropy) / 8 + 16; rndbuf = malloc(rndbuflen); pwlen = make_passwd(NULL, rndbuflen, type, entropy, NULL, 0); pwchars = make_passwd(NULL, rndbuflen, type, entropy, NULL, 1); emax = make_passwd(NULL, rndbuflen, type, entropy, NULL, 3); assert(pwlen > 0 && pwchars > 0 && emax > entropy); password = malloc(pwlen + 1); if (!rndbuf || !password) { fprintf(stderr, "Memory allocation error!\n"); exit(1); } cols = (width + 2) / (challen + 1 + pwlen + 2); if (cols < 1) cols = 1; 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 (!regenerate) { 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); } } if (!regenerate) { /* 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' (Y/n)? ", fnout); if (!fgets(password1, sizeof(password1), stdin) || (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 */ if (regenerate) { fprintf(stderr, "Enter master key: "); fgets(password1, sizeof(password1), stdin); } else { 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 (regenerate) { if (*password1) password1[strlen(password1)-1] = 0; /* remove last character = LF */ normal_masterkey = password1; pwnorm(normal_masterkey); md_init(&md); md_add(&md, normal_masterkey, strlen(normal_masterkey)); md_close(&md, h); if (h[0] & (0xff - (1 << (8 - MASTERKEY_CHECKBITS)) + 1)) { fprintf(stderr, "\nIncorrect master key (invalid checkbits)!\n"); exit(1); } } else { if (strcmp(password1, password2)) { fprintf(stderr, "\nThe two entered passwords were not identical!\n"); exit(1); } if (*password1) password1[strlen(password1)-1] = 0; /* remove last character = LF */ } if (regenerate) fprintf(stderr, "\n\nRecreating one-time password list ...\n"); else fprintf(stderr, "\n\nGenerating new one-time passwords ...\n\n"); if (header_lines) { /* prepare header line that uniquely identifies this password list */ time(&t); strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M", localtime(&t)); strcpy(hostname, "???"); gethostname(hostname, sizeof(hostname)); hostname[sizeof(hostname)-1] = 0; snprintf(header, sizeof(header), regenerate ? "OTPW list regenerated %s on %s" NL NL : "OTPW list generated %s on %s" NL NL, timestr, hostname); } /* allocate buffer for hash values */ hbuf = malloc(pages * rows * cols * hbuflen); if (!hbuf) { fprintf(stderr, "Memory allocation error!\n"); exit(1); } assert(MASTERKEY_CHECKBITS < 8); if (use_masterkey) { mklen = make_passwd(0, 0, key_type, key_entropy + MASTERKEY_CHECKBITS, NULL, 0) + 1; masterkey = malloc(mklen); normal_masterkey = malloc(mklen); if (!masterkey || !normal_masterkey) { fprintf(stderr, "Memory allocation error!\n"); exit(1); } do { /* generate new masterkey */ rbg_iter(r); random_string(r, MD_LEN, rndbuf, rndbuflen); make_passwd(rndbuf, rndbuflen, key_type, key_entropy + MASTERKEY_CHECKBITS, masterkey, mklen); strcpy(normal_masterkey, masterkey); pwnorm(normal_masterkey); md_init(&md); md_add(&md, normal_masterkey, strlen(normal_masterkey)); md_close(&md, h); /* until its hash has the first MASTERKEY_CHECKBITS zero */ } while (h[0] & (0xff - (1 << (8 - MASTERKEY_CHECKBITS)) + 1)); fprintf(stderr, "Master key: %s\n" "(Option -k will recreate the same password list " "from this key.)\n\n", masterkey); } for (l = 0; l < pages; l++) { if (header_lines) fputs(header, stdout); for (i = 0; i < rows; i++) { for (j = 0; j < cols; j++) { k = j * rows + i + l * rows * cols; snprintf(challenge, sizeof(challenge), "%03d", k); /* generate new password ... */ if (use_masterkey || regenerate) { /* ... from masterkey and challenge string */ md_init(&md); md_add(&md, normal_masterkey, strlen(normal_masterkey)); md_add(&md, challenge, strlen(challenge)); md_close(&md, h); random_string(h, MD_LEN, rndbuf, rndbuflen); } else { /* ... randomly */ rbg_iter(r); random_string(r, MD_LEN, rndbuf, rndbuflen); } make_passwd(rndbuf, rndbuflen, type, entropy, password, pwlen + 1); /* output challenge */ printf("%s %s", challenge, password); if (j < cols - 1) printf(" "); if (!regenerate) { /* hash password1 + pwnorm(password) and save result */ md_init(&md); md_add(&md, password1, strlen(password1)); pwnorm(password); md_add(&md, password, pwchars); md_close(&md, h); sprintf(hbuf + k * hbuflen, "%0*d", challen, k); conv_base64(hbuf + k*hbuflen + challen, h, otpw_hlen); } } if (i < rows - 1) printf(NL); } if (header_lines) { printf(NL NL "%*s", (cols*(challen + 1 + pwlen + 2) - 2)/2 + 50/2, "!!! REMEMBER: Enter the PREFIX PASSWORD first !!!"); } if (l < pages - 1) printf(FF); else printf(NL); } /* paranoia RAM scrubbing (note that we can't scrub stdout/stdin portably) */ rbg_iter(r); rbg_iter(r); 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)); memset(password, 0xaa, pwlen); fclose(stdout); if (regenerate) exit(0); /* create new hash file */ fprintf(stderr, "Creating '%s' ...\n", fnout); fntmp = (char *) malloc(strlen(fnout)+strlen(tmpsuffix)+1); if (!fntmp) abort(); strcpy(fntmp, fnout); strcat(fntmp, tmpsuffix); f = fopen(fntmp, "w"); if (!f) { fprintf(stderr, "Can't write to '%s", fntmp); perror("'"); exit(1); } if (fchmod(fileno(f), S_IRUSR | S_IWUSR)) { fprintf(stderr, "Can't fchmod '%s", fntmp); perror("'"); exit(1); } /* write magic code for format identification */ fprintf(f, "%s", otpw_magic); fprintf(f, "%d %d %d %d\n", pages * rows * cols, challen, otpw_hlen, pwchars); /* output all hash values in random permutation order */ if (random_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); } } else { for (k = 0; k < pages * rows * cols; k++) fprintf(f, "%s\n", hbuf + k*hbuflen); } fclose(f); if (rename(fntmp, fnout)) { fprintf(stderr, "Can't rename '%s' to '%s", fntmp, fnout); perror("'"); exit(1); } free(fntmp); /* if we overwrite OTPW file, then any remaining lock is now meaningless */ unlock: fntmp = (char *) malloc(strlen(fnout)+strlen(otpw_locksuffix)+1); if (!fntmp) abort(); strcpy(fntmp, fnout); strcat(fntmp, otpw_locksuffix); if (unlink(fntmp)) { if (errno != ENOENT) { fprintf(stderr, "Can't delete lock file '%s", fntmp); perror("'"); exit(1); } } else { fprintf(stderr, "Deleted lock file '%s'\n", fntmp); } free(fntmp); return 0; } otpw-1.5/otpw-l.c000066400000000000000000000011361237075065300137470ustar00rootroot00000000000000/* * 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 */ #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-1.5/otpw.c000066400000000000000000000450511237075065300135220ustar00rootroot00000000000000/* * One-time password login library * * Markus Kuhn */ #include #include #include #include #include #include #include #include #include #include #include #include #include "otpw.h" #include "md.h" #ifndef DEBUG_LOG #define DEBUG_LOG(...) if (ch->flags & OTPW_DEBUG) \ { fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); } #endif /* Some global variables with configuration options */ /* Path for the one-time password file relative to home directory of * the user who tries to log in. (Ignored if otpw_pseudouser != NULL) */ char *otpw_file = ".otpw"; /* Suffix added to the one-time password filename to create lock symlink */ char *otpw_locksuffix = ".lock"; /* Number of passwords requested while another one is locked. */ int otpw_multi = 3; /* Age of a lock file in seconds after which it will be deleted. */ double otpw_locktimeout = 24 * 60 * 60; /* Length of stored hash in characters (each encoding 6 bits) */ int otpw_hlen = 12; /* Characteristic first line, for recognition of an OTPW file */ char *otpw_magic = "OTPW1\n"; /* * Normally, the password file is located in the home directory of the * user who tries to log in, typically in the file ~/.otpw, and is * accessed using the effective uid and gid of that user. However, * otpw_pseudouser is not NULL, then the uid/gid of that pseudouser * (e.g., "otpw") will be used instead to access the file, which will * be located in the home directory of the pseudouser (e.g., * "/var/lib/otpw"), and will have the name of the user who tries to * log in instead of ".otpw". */ struct otpw_pwdbuf *otpw_pseudouser = NULL; char *otpw_autopseudouser = "otpw"; long otpw_autopseudouser_maxuid = 999; /* allocate a struct otpw_pwdbuf (of suitable size to also hold the strings) */ static struct otpw_pwdbuf *otpw_malloc_pwdbuf(void) { struct otpw_pwdbuf *p; long buflen; buflen = sysconf(_SC_GETPW_R_SIZE_MAX); /* typical value: 1024 */ /* fprintf(stderr, "_SC_GETPW_R_SIZE_MAX = %ld\n", buflen); */ if (buflen < 0) return NULL; p = (struct otpw_pwdbuf *) malloc(sizeof(struct otpw_pwdbuf) + buflen); if (p) p->buflen = buflen; return p; } /* mallocating variant of getpwnam_r */ int otpw_getpwnam(const char *name, struct otpw_pwdbuf **result) { struct otpw_pwdbuf *p; struct passwd *r; int err = ENOMEM; if ((p = otpw_malloc_pwdbuf())) { err = getpwnam_r(name, &p->pwd, p->buf, p->buflen, &r); if (r) { *result = p; } else { *result = NULL; free(p); } } return err; } /* mallocating variant of getpwuid_r */ int otpw_getpwuid(uid_t uid, struct otpw_pwdbuf **result) { struct otpw_pwdbuf *p; struct passwd *r; int err = ENOMEM; if ((p = otpw_malloc_pwdbuf())) { err = getpwuid_r(uid, &p->pwd, p->buf, p->buflen, &r); if (r) { *result = p; } else { *result = NULL; free(p); } } return err; } /* * Check if the user otpw_autopseudouser exists and has a UID of not * higher than otpw_autopseudouser_maxuid. If so, malloc and set * pseudouser accordingly. Usually: pseudouser == &otpw_pseudouser. */ int otpw_set_pseudouser(struct otpw_pwdbuf **pseudouser) { int err; err = otpw_getpwnam(otpw_autopseudouser, pseudouser); if (otpw_pseudouser) { if (otpw_autopseudouser_maxuid >= 0 && otpw_pseudouser->pwd.pw_uid > otpw_autopseudouser_maxuid) { err = EINVAL; free(*pseudouser); otpw_pseudouser = NULL; } } return err; } /* * A random bit generator. Hashes together some quick sources of entropy * to provide some reasonable random seed. (High entropy is not security * critical here.) */ static 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); } static 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). */ static 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'; } static void otpw_free(struct challenge *ch) { int i; if (ch->selection) free(ch->selection); if (ch->hash) { for (i = 0; i < otpw_multi; i++) { if (ch->hash[i]) free(ch->hash[i]); } free(ch->hash); } if (ch->filename) free(ch->filename); if (ch->lockfilename) free(ch->lockfilename); } 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 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; ch->filename = NULL; ch->lockfilename = NULL; ch->selection = NULL; ch->hash = NULL; ch->selection = (int *) calloc(otpw_multi, sizeof(int)); ch->hash = (char **) calloc(otpw_multi, sizeof(char *)); if (!ch->selection || !ch->hash) { DEBUG_LOG("calloc() failed"); goto cleanup; } if (!user) { DEBUG_LOG("No password database entry provided!"); goto cleanup; } /* prepare filename of one-time password file */ if (otpw_pseudouser) { ch->filename = (char *) malloc(strlen(otpw_pseudouser->pwd.pw_dir) + 1 + strlen(user->pw_name) + 1); if (!ch->filename) { DEBUG_LOG("malloc() for ch->filename failed"); goto cleanup; } strcpy(ch->filename, otpw_pseudouser->pwd.pw_dir); strcat(ch->filename, "/"); strcat(ch->filename, user->pw_name); ch->uid = otpw_pseudouser->pwd.pw_uid; ch->gid = otpw_pseudouser->pwd.pw_gid; } else { ch->filename = (char *) malloc(strlen(user->pw_dir)+1+strlen(otpw_file)+1); if (!ch->filename) { DEBUG_LOG("malloc() for ch->filename failed"); goto cleanup; } strcpy(ch->filename, user->pw_dir); strcat(ch->filename, "/"); strcat(ch->filename, otpw_file); ch->uid = user->pw_uid; ch->gid = user->pw_gid; } /* prepare associated lock filename */ ch->lockfilename = (char *) malloc(strlen(ch->filename) + strlen(otpw_locksuffix) + 1); if (!ch->lockfilename) { DEBUG_LOG("malloc() for ch->lockfilename failed"); goto cleanup; } strcpy(ch->lockfilename, ch->filename); strcat(ch->lockfilename, otpw_locksuffix); /* set effective uid/gid 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 (!(f = fopen(ch->filename, "r"))) { DEBUG_LOG("fopen(\"%s\", \"r\"): %s", ch->filename, 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, &ch->challen, &ch->hlen, &ch->pwlen) != 4) { DEBUG_LOG("Header wrong in '%s'!", ch->filename); goto cleanup; } if (ch->entries < 1 || ch->entries > 9999 || ch->challen < 1 || (ch->challen + 1) * otpw_multi > (int)sizeof(ch->challenge) || ch->pwlen < 4 || ch->pwlen > 999 || ch->hlen != otpw_hlen) { DEBUG_LOG("Header parameters (%d %d %d %d) out of allowed range!", ch->entries, ch->challen, ch->hlen, ch->pwlen); goto cleanup; } hbuflen = ch->challen + ch->hlen; hbuf = malloc(ch->entries * hbuflen); if (!hbuf) { DEBUG_LOG("malloc() for hbuf failed"); goto cleanup; } 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("%s too short!", ch->filename); 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, ch->challen); ch->challenge[ch->challen] = 0; ch->selection[0] = j; ch->hash[0] = (char *) calloc(ch->hlen + 1, sizeof(char)); if (!ch->hash[0]) { DEBUG_LOG("calloc() failed"); goto cleanup; } strncpy(ch->hash[0], hbuf + j*hbuflen + ch->challen, ch->hlen); 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, ch->lockfilename) == 0) { /* ok, we got the lock */ ch->passwords = 1; ch->locked = 1; goto cleanup; } if (errno != EEXIST) { DEBUG_LOG("symlink(\"%s\", \"%s\"): %s", ch->challenge, ch->lockfilename, strerror(errno)); ch->challenge[0] = 0; goto cleanup; } if (lstat(ch->lockfilename, &lbuf) == 0) { if (otpw_locktimeout > 0 && difftime(time(NULL), lbuf.st_mtime) > otpw_locktimeout) { /* remove a stale lock after a specified time out period */ unlink(ch->lockfilename); repeat = 1; } } else if (errno == ENOENT) repeat = 1; else { DEBUG_LOG("lstat(\"%s\", ...): %s", ch->lockfilename, 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(ch->lockfilename, lock, sizeof(lock)-1); if (i > 0) { lock[i] = 0; if ((int) strlen(lock) != ch->challen) { /* lock symlink seems to have been corrupted */ DEBUG_LOG("Removing corrupt lock symlink to %s -> %s.", ch->lockfilename, lock); unlink(ch->lockfilename); } } else if (errno != ENOENT) { DEBUG_LOG("Could not read lock symlink '%s'.", ch->lockfilename); 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) - ch->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 + ch->challen, lock, ch->challen)) && count++ < 2 * ch->entries); /* fallback scan for remaining password */ while (hbuf[j*hbuflen] == '-' || !strncmp(hbuf + j*hbuflen + ch->challen, lock, ch->challen)) j = (j + 1) % ch->entries; /* add password j to multi challenge */ sprintf(ch->challenge + strlen(ch->challenge), "%s%.*s", ch->passwords ? "/" : "", ch->challen, hbuf + j*hbuflen); if (!ch->hash[ch->passwords]) ch->hash[ch->passwords] = (char *) calloc(ch->hlen + 1, sizeof(char)); if (!ch->hash[ch->passwords]) { DEBUG_LOG("calloc() failed"); goto cleanup; } strncpy(ch->hash[ch->passwords], hbuf + j*hbuflen + ch->challen, ch->hlen); 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); if (!ch->challenge[0]) otpw_free(ch); 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."); goto cleanup; } otpw = calloc(ch->passwords, ch->pwlen); if (!otpw) { DEBUG_LOG("malloc failed"); goto cleanup; } /* 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 ch->filename */ md_close(&md, h); conv_base64(line, h, ch->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 ch->filename */ if (!(f = fopen(ch->filename, "r+"))) { DEBUG_LOG("Failed getting write access to '%s': %s", ch->filename, 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 != ch->hlen || challen != ch->challen || (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(ch->lockfilename)) 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); otpw_free(ch); return result; } otpw-1.5/otpw.h000066400000000000000000000101041237075065300135160ustar00rootroot00000000000000/* * One-time password login library * * Markus Kuhn */ #ifndef OTPW_H #define OTPW_H #include #include #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 challen; /* number of characters in challenge string */ int hlen; /* number of characters in hash value */ int remaining; /* number of remaining unused OTPW file entries */ uid_t uid; /* effective uid for OTPW file/lock access */ gid_t gid; /* effective gid for OTPW file/lock access */ int *selection; /* position of the otpw_multi requested passwords */ char **hash; /* base64 hash values of the otpw_multi requested passwords, each otpw_hlen+1 bytes long */ int flags; /* 1 : debug messages, 2: no locking */ char *filename; /* path of .otpw file (malloc'ed) */ char *lockfilename; /* path of .optw.lock file (malloc'ed) */ }; /* * Call otpw_prepare() after the user has entered their login name and * has requested OTPW authentication, and after and you have retrieved * their password database entry *user. After the call, ch->challenge * will contain a string that you have to present to the user before * they can enter the password. If ch->challenge[0] == 0 then one-time * password authentication is not possible at this time. Once you have * received the password, pass it to otpw_verify() along with the same * struct *ch used here. */ 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 * password that the user has provided ('\0' terminated). Accept the * user if and only if the return value is OTPW_OK. * * IMPORTANT: If otpw_prepare() returned a non-empty challenge string * (ch->challenge[0] != 0), 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. * * After a successful login, check whether ch->entries > 2 * * ch->remaining and remind the user to generate new passwords if * so. */ int otpw_verify(struct challenge *ch, char *password); /* buffer to hold the result of getpwnam_r() or getpwuid_r(); * essentially a struct passwd plus space for the strings * that it might refer to */ struct otpw_pwdbuf { struct passwd pwd; size_t buflen; char buf[0]; /* actual size is buflen if allocated by otpw_malloc_pwdbuf() */ }; /* some functions for dealing with struct pwdbuf */ int otpw_getpwnam(const char *name, struct otpw_pwdbuf **result); int otpw_getpwuid(uid_t uid, struct otpw_pwdbuf **result); /* * Check if the user otpw_autopseudouser exists and had a UID of not * higher than otpw_autopseudouser_maxuid. If so, malloc and set * otpw_pseudouser accordingly. */ int otpw_set_pseudouser(); /* some global variables with configuration options */ extern char *otpw_file; extern char *otpw_locksuffix; extern int otpw_multi; extern int otpw_hlen; extern char *otpw_magic; extern double otpw_locktimeout; extern struct otpw_pwdbuf *otpw_pseudouser; #endif otpw-1.5/otpw.html000066400000000000000000000601371237075065300142460ustar00rootroot00000000000000 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.5, 2014-08-07

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, either in the user’s home directory or in a dedicated system 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 containing hash values of passwords. Depending on the installation option chosen, this can either be a file ~john/.otpw located in the user’s home directory, or it can be a file ~otpw/john in the home directory of a dedicated pseudo user “otpw”. In the latter case, the otpw-gen tool for generating new passwords must be owned by pseudo user “otpw” and have the SETUID bit set. As long as users do not have such a hash file, the one-time-password facility is not active for them.

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 https://www.cl.cam.ac.uk/~mgk25/download/ or use

  git clone https://www.cl.cam.ac.uk/~mgk25/git/otpw

First have a look into otpw.c and otpw-gen.c where some system parameters can be changed. The only item there really worth being checked carefully is entropy_cmds. 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

With OpenSSH 5.9p1 for example, you need to make sure that it has PAM support compiled in, and you will need in /etc/ssh/sshd_config the lines

  UsePrivilegeSeparation            no
  UsePAM                            yes
  ChallengeResponseAuthentication   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”.

Pseudo user setup

Starting with otpw-1.5, if you would like to keep the password hash files out of the reach of your users, you can create a pseudo user “otpw” (uid < 1000) and an associated home directory where the password files will be stored. On Ubuntu Linux, for example, the steps necessary for such a setup might be

  $ adduser --system --gecos 'OTPW pseudouser' --home /var/lib/otpw otpw
  $ chown otpw /usr/bin/otpw-gen && chmod u+s /usr/bin/otpw-gen

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 -p) 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–5.9 (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–2014 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 2014-08-07 – https://www.cl.cam.ac.uk/~mgk25/otpw.html otpw-1.5/pam_otpw.8000066400000000000000000000040151237075065300142770ustar00rootroot00000000000000.TH PAMOTPW 8 "2014-08-07" .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 PSEUDO-USER INSTALLATION If a system pseudo user “otpw” exists in the user database (with UID < 1000), then the password hash files will not be stored in the user's home directory. Instead of looking for .B ~john/.otpw.lock the file has to be located in the home directory of the pseudo user “otpw”, and be named after the user (e.g. “/var/lib/otpw/john”). It will be accessed with the effective UID and GID of that pseudo user. .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-1.5/pam_otpw.c000066400000000000000000000241261237075065300143570ustar00rootroot00000000000000/* * One-time password login PAM module * * 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) * */ #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); openlog(logname, LOG_CONS | LOG_PID, LOG_AUTH); 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 otpw_pwdbuf *user; 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.) */ otpw_getpwnam(username, &user); if (!user) { 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; } /* check whether a pseudo-user for owning OTPW files exist */ otpw_set_pseudouser(&otpw_pseudouser); /* prepare OTPW challenge */ otpw_prepare(ch, &user->pwd, otpw_flags); free(user); if (otpw_pseudouser) { free(otpw_pseudouser); otpw_pseudouser = NULL; } 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-1.5/rmd160.c000066400000000000000000000217731237075065300135470ustar00rootroot00000000000000/********************************************************************\ * * 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-1.5/rmd160.h000066400000000000000000000104721237075065300135460ustar00rootroot00000000000000/********************************************************************\ * * 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 ***********************/