Configuration
To use the module, first make sure that it is accessible as /lib/security/pam_oath.so:
# ln -s /usr/local/lib/security/pam_oath.so /lib/security/pam_oath.so
# ls -la /lib/security/pam_oath.so
lrwxrwxrwx 1 root root 35 Dec 4 10:49 /lib/security/pam_oath.so -> /usr/local/lib/security/pam_oath.so
#
The next step is to configure PAM to use the module. One simple way for testing is to configure it for "su". Make sure you have a root window open before making any changes!
# head -1 /etc/pam.d/su
auth [user_unknown=ignore success=ok] pam_oath.so debug usersfile=/etc/users.oath window=20
#
The next step is to create a user credentials file. As you can see, the PAM configuration uses /etc/users.oath for this. So we create the file:
# cat>/etc/users.oath
HOTP root - 00
# chmod go-rw /etc/users.oath
#
The file format handles any whitespace as field separator. You may also add lines starting with # for comments. The file format is documented here: http://code.google.com/p/mod-authn-otp/wiki/UsersFile
WARNING! The above added an OATH secret of all-zeros, which leads to no security. In production, replace "00" with a randomly generate hex encoded data of say, 20 bytes in size.
To test the setup, we need to generate some one-time passwords. The "oathtool" is handy for this purpose. Replace 00 with the key you generated.
# oathtool/oathtool -w 5 00
328482
812658
073348
887919
320986
435986
#
So let’s test this by running su. At the prompt, you type the password (in this example, "pw") concatenated with the OTP (in this example, "328482").
jas@mocca:~$ su
[pam_oath.c:parse_cfg(122)] called.
[pam_oath.c:parse_cfg(123)] flags 0 argc 4
[pam_oath.c:parse_cfg(125)] argv[0]=alwaysok
[pam_oath.c:parse_cfg(125)] argv[1]=debug
[pam_oath.c:parse_cfg(125)] argv[2]=usersfile=/etc/users.oath
[pam_oath.c:parse_cfg(125)] argv[3]=window=20
[pam_oath.c:parse_cfg(126)] debug=1
[pam_oath.c:parse_cfg(127)] alwaysok=1
[pam_oath.c:parse_cfg(128)] try_first_pass=0
[pam_oath.c:parse_cfg(129)] use_first_pass=0
[pam_oath.c:parse_cfg(130)] usersfile=/etc/users.oath
[pam_oath.c:parse_cfg(131)] digits=0
[pam_oath.c:parse_cfg(132)] window=20
[pam_oath.c:pam_sm_authenticate(162)] get user returned: root
One-time password (OATH) for `root':
[pam_oath.c:pam_sm_authenticate(237)] conv returned: 328482
[pam_oath.c:pam_sm_authenticate(297)] OTP: 328482
[pam_oath.c:pam_sm_authenticate(308)] authenticate rc 0 last otp Wed Jun 3 19:22:50 1931
[pam_oath.c:pam_sm_authenticate(329)] done. [Success]
[pam_oath.c:pam_sm_setcred(341)] called.
[pam_oath.c:pam_sm_setcred(347)] retval: 0
[pam_oath.c:pam_sm_setcred(367)] done. [Success]
mocca:/home/jas#
Success!
The code should have updated the /etc/users.oath file to record the incremented counter, last OTP and timestamp. Inspect the new value as follows:
# cat /etc/users.oath
HOTP root - 00 0 328482 2009-12-07T23:23:49L
#
In production use you will want to remove the debug parameter in the PAM configuration, and make sure you use random keys instead of 00.
Two-factor authentication
To also verify passwords, you need to configure the one-time password length for the PAM module:
# head -1 /etc/pam.d/su
auth requisite pam_oath.so usersfile=/etc/users.oath window=20 digits=6
#
Next specify a password in the users.oath file:
# cat>/etc/users.oath
HOTP root pw 00
# chmod go-rw /etc/users.oath
#
Then test it:
jas@mocca:~$ su
[pam_oath.c:parse_cfg(122)] called.
[pam_oath.c:parse_cfg(123)] flags 0 argc 5
[pam_oath.c:parse_cfg(125)] argv[0]=alwaysok
[pam_oath.c:parse_cfg(125)] argv[1]=debug
[pam_oath.c:parse_cfg(125)] argv[2]=usersfile=/etc/users.oath
[pam_oath.c:parse_cfg(125)] argv[3]=window=20
[pam_oath.c:parse_cfg(125)] argv[4]=digits=6
[pam_oath.c:parse_cfg(126)] debug=1
[pam_oath.c:parse_cfg(127)] alwaysok=1
[pam_oath.c:parse_cfg(128)] try_first_pass=0
[pam_oath.c:parse_cfg(129)] use_first_pass=0
[pam_oath.c:parse_cfg(130)] usersfile=/etc/users.oath
[pam_oath.c:parse_cfg(131)] digits=6
[pam_oath.c:parse_cfg(132)] window=20
[pam_oath.c:pam_sm_authenticate(163)] get user returned: root
One-time password (OATH) for `root':
[pam_oath.c:pam_sm_authenticate(238)] conv returned: pw820368
[pam_oath.c:pam_sm_authenticate(279)] Password: pw
[pam_oath.c:pam_sm_authenticate(297)] OTP: 820368
[pam_oath.c:pam_sm_authenticate(308)] authenticate rc 0 last otp Mon Jun 1 20:43:54 1931
[pam_oath.c:pam_sm_authenticate(330)] done. [Success]
[pam_oath.c:pam_sm_setcred(342)] called.
[pam_oath.c:pam_sm_setcred(348)] retval: 0
[pam_oath.c:pam_sm_setcred(368)] done. [Success]
mocca:/home/jas#
If you supply an incorrect password, you’ll get:
jas@mocca:~$ su
[pam_oath.c:parse_cfg(122)] called.
[pam_oath.c:parse_cfg(123)] flags 0 argc 5
[pam_oath.c:parse_cfg(125)] argv[0]=alwaysok
[pam_oath.c:parse_cfg(125)] argv[1]=debug
[pam_oath.c:parse_cfg(125)] argv[2]=usersfile=/etc/users.oath
[pam_oath.c:parse_cfg(125)] argv[3]=window=20
[pam_oath.c:parse_cfg(125)] argv[4]=digits=6
[pam_oath.c:parse_cfg(126)] debug=1
[pam_oath.c:parse_cfg(127)] alwaysok=1
[pam_oath.c:parse_cfg(128)] try_first_pass=0
[pam_oath.c:parse_cfg(129)] use_first_pass=0
[pam_oath.c:parse_cfg(130)] usersfile=/etc/users.oath
[pam_oath.c:parse_cfg(131)] digits=6
[pam_oath.c:parse_cfg(132)] window=20
[pam_oath.c:pam_sm_authenticate(163)] get user returned: root
One-time password (OATH) for `root':
[pam_oath.c:pam_sm_authenticate(238)] conv returned: grr525990
[pam_oath.c:pam_sm_authenticate(279)] Password: grr
[pam_oath.c:pam_sm_authenticate(297)] OTP: 525990
[pam_oath.c:pam_sm_authenticate(308)] authenticate rc -8 last otp Tue Jun 2 19:29:14 1931
[pam_oath.c:pam_sm_authenticate(314)] One-time password not authorized to login as user 'root'
[pam_oath.c:pam_sm_authenticate(327)] alwaysok needed (otherwise return with 9)
[pam_oath.c:pam_sm_authenticate(330)] done. [Success]
[pam_oath.c:pam_sm_setcred(342)] called.
[pam_oath.c:pam_sm_setcred(348)] retval: 0
[pam_oath.c:pam_sm_setcred(368)] done. [Success]
mocca:/home/jas#
List of all parameters
"debug": Enable debug output to stdout.
"alwaysok": Enable that all authentication attempts should succeed
(aka presentation mode).
"try_first_pass": Before prompting the user for their password, the
module first tries the previous stacked module´s
password in case that satisfies this module as
well.
"use_first_pass": The argument use_first_pass forces the module to
use a previous stacked modules password and will
never prompt the user - if no password is
available or the password is not appropriate, the
user will be denied access.
"usersfile": Specify filename where credentials are stored, for
example "/etc/users.oath". The placeholder values
"${USER}" and "${HOME}" may be used to specify the
filename on a per-user basis. The values "${USER}" and
"${HOME}" expand to the user's username and home
directory, respectively. See below on file protection.
"digits": Specify number of digits in the one-time password,
required when using passwords in usersfile. Supported
values are 6, 7, and 8.
"window": Specify search depth, an integer typically from 5 to 50
but other values can be useful too.
Usersfile protection
The PAM module performs its operations as root when re-writing the "usersfile". Even if the file is owned by a non-root user, the file modifications are performed as the root user and file ownership is reset back to the original file owner.
Thus "usersfile" MUST point to a protected file where non-root users cannot modify it.
module will temporarily drop the effective user id to the user that is being logged in via PAM before accessing and rewriting the file.
The following are the typicaly scenarios for the usersfile placement, and its recommended file protection. Note that a root-owned file below a path controlled by a non-root user lead to a vulnerable configuration.
1) usersfile=/etc/users.oath
Root owned that is read/write-protected against users, for centralized control.
User-owned file that is read/write-protected against other users.
The disadvantage is that user can read their OATH secret, and potentially damage their ability to login by messing up the file, but in some situations that may be an advantage.
Root-owned per-user files that are all read/write-protected against non-root users. Typically more relevant for larger multi-user system where you want to split up the /etc/users.oath file on a per-user basis, for increased performance and/or administrative control.
WARNING! Don’t use usersfile=/home/foo/bar/oath.secrets when
/home/foo has write-permission for non-root user foo
even when the
oath.secrets
file happen to be owned by root, as the user may use
symbolic links at /home/foo/bar to redirection which file is opened.
SSH Configuration
Configuring pam_oath for use with SSH is straight forward, however you should make sure the /etc/ssh/sshd_config file contains the following:
UsePAM yes
ChallengeResponseAuthentication yes
Another reported cause of problems has been SELinux, and you could experiment with disabling SELinux by doing "/usr/sbin/setenforce 0".
Alternatively, instead of "ChallengeResponseAuthentication" it should work to enable "PasswordAuthentication" instead.
Debug hint
By using a /lib/security symlink, you may point the module to a file in your build directory (for testing purposes):
# ln -s /home/jas/src/oath-toolkit/pam_oath/.libs/pam_oath.so /lib/security/pam_oath.so
# ls -la /lib/security/pam_oath.so
lrwxrwxrwx 1 root root 53 Dec 4 10:49 /lib/security/pam_oath.so -> /home/jas/src/oath-toolkit/pam_oath/.libs/pam_oath.so
#
This makes it easy to test newly built code.