OATH Toolkit pam_oath/README
Copyright (C) 2009-2024 Simon Josefsson.  Licensed under the GPLv3+.

This directory holds an PAM module for authenticating users using OATH
one-time password technology.

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.

  "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.

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.
