├── .github └── workflows │ ├── build_and_test.yml │ └── codeql-analysis.yml ├── .gitignore ├── AUTHORS ├── BLURB ├── COPYING ├── Makefile.am ├── NEWS ├── README ├── README.adoc ├── build-aux └── config.rpath ├── configure.ac ├── doc ├── Authentication_Using_Challenge-Response.adoc ├── MacOS_X_Challenge-Response.adoc ├── Two_Factor_PAM_Configuration.adoc ├── Ubuntu_FreeRadius_YubiKey.adoc ├── YubiKey_and_FreeRADIUS_1FA_via_PAM.adoc ├── YubiKey_and_FreeRADIUS_via_PAM.adoc ├── YubiKey_and_OpenVPN_via_PAM.adoc ├── YubiKey_and_Radius_via_PAM.adoc ├── YubiKey_and_SELinux.adoc └── YubiKey_and_SSH_via_PAM.adoc ├── drop_privs.c ├── drop_privs.h ├── m4 ├── lib-ld.m4 ├── lib-link.m4 ├── lib-prefix.m4 ├── manywarnings.m4 └── warnings.m4 ├── mac.mk ├── pam_yubico.8.txt ├── pam_yubico.c ├── tests ├── Makefile.am ├── aux │ ├── auth_mapping.sql │ ├── authfile │ ├── build-and-test.sh │ ├── ldap.pl │ └── ykval.pl ├── pam_test.c └── util_test.c ├── util.c ├── util.h ├── ykbzero.h ├── ykpamcfg.1.txt ├── ykpamcfg.c └── yubikey_mapping.sql /.github/workflows/build_and_test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ${{ matrix.os }} 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | include: 10 | - os: ubuntu-20.04 11 | config_args: "--without-mysql" 12 | extra: "libldap2-dev libykpers-1-dev libnet-ldap-server-perl" 13 | - os: ubuntu-20.04 14 | config_args: "--without-ldap --without-mysql" 15 | extra: "libykpers-1-dev" 16 | - os: ubuntu-20.04 17 | config_args: "--without-cr --without-mysql" 18 | extra: "libldap2-dev libnet-ldap-server-perl" 19 | - os: ubuntu-20.04 20 | config_args: "--without-ldap --without-cr --without-mysql" 21 | extra: "" 22 | - os: ubuntu-20.04 23 | config_args: "" 24 | extra: "libldap2-dev libykpers-1-dev libnet-ldap-server-perl libmysqlclient-dev" 25 | mysql: true 26 | - os: ubuntu-18.04 27 | config_args: "" 28 | extra: "libldap2-dev libykpers-1-dev libnet-ldap-server-perl libmysqlclient-dev" 29 | mysql: true 30 | - os: ubuntu-16.04 31 | config_args: "" 32 | extra: "libldap2-dev libykpers-1-dev libnet-ldap-server-perl libmysqlclient-dev" 33 | mysql: true 34 | services: 35 | mariadb: 36 | image: mariadb:latest 37 | ports: 38 | - 3306 39 | env: 40 | MYSQL_USER: user 41 | MYSQL_PASSWORD: password 42 | MYSQL_DATABASE: otp 43 | MYSQL_ROOT_PASSWORD: password 44 | options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 45 | steps: 46 | - uses: actions/checkout@v1 47 | - name: Setup Database 48 | if: ${{ matrix.mysql }} 49 | env: 50 | MYSQL_PORT: ${{ job.services.mariadb.ports[3306] }} 51 | run: | 52 | while ! mysqladmin ping -h"127.0.0.1" -P"$MYSQL_PORT" --silent; do 53 | sleep 1 54 | done 55 | mysql --user=user --password=password --host=127.0.0.1 --port=$MYSQL_PORT otp < yubikey_mapping.sql 56 | mysql --user=user --password=password --host=127.0.0.1 --port=$MYSQL_PORT otp < tests/aux/auth_mapping.sql 57 | - name: Build and test 58 | env: 59 | CONFIGURE_ARGS: ${{ matrix.config_args }} 60 | EXTRA: ${{ matrix.extra }} 61 | MYSQL_PORT: ${{ job.services.mariadb.ports[3306] }} 62 | run: | 63 | tests/aux/build-and-test.sh 64 | - name: Display logs 65 | if: ${{ always() }} 66 | run: | 67 | for log in tests/*.log; do 68 | echo $log 69 | cat $log 70 | done 71 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '0 23 * * 5' 8 | 9 | jobs: 10 | CodeQL-Build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v2 17 | with: 18 | # We must fetch at least the immediate parents so that if this is 19 | # a pull request then we can checkout the head. 20 | fetch-depth: 2 21 | 22 | # If this run was triggered by a pull request event, then checkout 23 | # the head of the pull request instead of the merge commit. 24 | - run: git checkout HEAD^2 25 | if: ${{ github.event_name == 'pull_request' }} 26 | 27 | # Initializes the CodeQL tools for scanning. 28 | - name: Initialize CodeQL 29 | uses: github/codeql-action/init@v1 30 | 31 | - name: Build yubico-pam 32 | run: | 33 | sudo apt update 34 | sudo apt install -y libykclient-dev libykpers-1-dev libyubikey-dev \ 35 | libpam-dev help2man asciidoc-base libmysqlclient-dev 36 | autoreconf --install 37 | ./configure 38 | make 39 | 40 | - name: Perform CodeQL Analysis 41 | uses: github/codeql-action/analyze@v1 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .*~ 3 | .deps/ 4 | .libs/ 5 | ChangeLog 6 | INSTALL 7 | Makefile 8 | Makefile.in 9 | \#* 10 | aclocal.m4 11 | autom4te.cache 12 | build-aux 13 | config.guess 14 | config.log 15 | config.status 16 | config.sub 17 | configure 18 | depcomp 19 | install-sh 20 | libtool 21 | ltmain.sh 22 | m4/libtool.m4 23 | m4/ltoptions.m4 24 | m4/ltsugar.m4 25 | m4/ltversion.m4 26 | m4/lt~obsolete.m4 27 | missing 28 | pam_yubico-*.tar.gz 29 | pam_yubico-*.tar.gz.sig 30 | pam_yubico-*.pkg 31 | pam_yubico.8 32 | ykpamcfg.1 33 | drop_privs.lo 34 | libpam_util.la 35 | pam_yubico.la 36 | pam_yubico.lo 37 | util.lo 38 | ykpamcfg 39 | ykpamcfg.o 40 | tests/test 41 | tests/test.o 42 | tests/util_test 43 | tests/util_test-util_test.o 44 | tests/util_test.log 45 | tests/util_test.o 46 | tests/util_test.trs 47 | tests/test-suite.log 48 | tests/test.log 49 | tests/test.trs 50 | core 51 | libpam_real.la 52 | tests/core 53 | tests/pam_test 54 | tests/pam_test.log 55 | tests/pam_test.o 56 | tests/pam_test-pam_test.o 57 | tests/pam_test.trs 58 | coverage/ 59 | tests/pam_test.gcno 60 | ykpamcfg.gcno 61 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Maintainer 2 | ---------- 3 | 4 | Simon Josefsson 5 | Klas Lindfors 6 | 7 | Contributors 8 | ------- 9 | 10 | Tollef Fog Heen 11 | Challenge-response support for offline use. 12 | 13 | zubrick433@gmail.com 14 | LDAP support. 15 | 16 | Erinn Looney-Triggs 17 | Documentation. 18 | 19 | qistoph@gmail.com 20 | verbose_otp option. 21 | 22 | Remi Mollon 23 | capath option. 24 | 25 | fraser.scott@gmail.com 26 | Configurable public_id length. 27 | 28 | judas.iscariote, maxsanna81@gmail.com 29 | LDAP fixes. 30 | 31 | Romain Riviere 32 | Debug macro fixes. 33 | 34 | Nanakos Chrysostomos 35 | Security fix. 36 | 37 | dr8 38 | Security fix (problem really in ykclient). 39 | 40 | Ricky Zhou 41 | Drop permissions before opening user files. 42 | 43 | Fredrik Thulin 44 | Maintainer alumni, helped on drop_privs.c, utils.c, and more. 45 | -------------------------------------------------------------------------------- /BLURB: -------------------------------------------------------------------------------- 1 | Author: Yubico 2 | Basename: yubico-pam 3 | Homepage: https://developers.yubico.com/yubico-pam/ 4 | License: BSD-2-Clause 5 | Name: yubico-pam 6 | Project: yubico-pam 7 | Summary: Yubico Pluggable Authentication Module (PAM) 8 | Travis: https://travis-ci.org/Yubico/yubico-pam 9 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2014 Yubico AB 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | # Written by Simon Josefsson . 2 | # Copyright (c) 2006-2014 Yubico AB 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are 7 | # met: 8 | # 9 | # * Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 12 | # * Redistributions in binary form must reproduce the above 13 | # copyright notice, this list of conditions and the following 14 | # disclaimer in the documentation and/or other materials provided 15 | # with the distribution. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | SUBDIRS = . tests 30 | 31 | ACLOCAL_AMFLAGS = -I m4 32 | 33 | AM_CFLAGS = $(WARN_CFLAGS) 34 | AM_CPPFLAGS = @YKPERS_CFLAGS@ 35 | 36 | libdir = $(PAMDIR) 37 | 38 | lib_LTLIBRARIES = pam_yubico.la 39 | 40 | pam_yubico_la_SOURCES = drop_privs.h drop_privs.c 41 | # XXX add -Wl,-x too? PAM documentation suggests it. 42 | pam_yubico_la_LIBADD = @LTLIBYUBIKEY@ @LTLIBYKCLIENT@ @LIBLDAP@ @LIBPAM@ 43 | pam_yubico_la_LIBADD += libpam_util.la libpam_real.la 44 | pam_yubico_la_LDFLAGS = -module -avoid-version 45 | 46 | noinst_LTLIBRARIES = libpam_util.la libpam_real.la 47 | libpam_util_la_SOURCES = util.c util.h 48 | libpam_util_la_LIBADD = @LTLIBYUBIKEY@ @YKPERS_LIBS@ 49 | 50 | # if MYSQL_SUPPORT 51 | AM_CFLAGS += @MYSQL_CFLAGS@ 52 | libpam_util_la_LIBADD += @MYSQL_LIBS@ 53 | # endif 54 | 55 | libpam_real_la_SOURCES = pam_yubico.c ykbzero.h 56 | 57 | # The command line tools. 58 | 59 | if YKPERS 60 | bin_PROGRAMS = ykpamcfg 61 | endif 62 | 63 | ykpamcfg_SOURCES = ykpamcfg.c 64 | ykpamcfg_LDADD = libpam_util.la 65 | 66 | if ENABLE_DOC 67 | if YKPERS 68 | dist_man1_MANS = ykpamcfg.1 69 | endif 70 | 71 | dist_man8_MANS = pam_yubico.8 72 | DISTCLEANFILES = $(dist_man1_MANS) $(dist_man8_MANS) 73 | 74 | MANSOURCES = pam_yubico.8.txt ykpamcfg.1.txt 75 | EXTRA_DIST = doc/Authentication_Using_Challenge-Response.adoc doc/MacOS_X_Challenge-Response.adoc doc/Two_Factor_PAM_Configuration.adoc doc/Ubuntu_FreeRadius_YubiKey.adoc doc/YubiKey_and_FreeRADIUS_1FA_via_PAM.adoc doc/YubiKey_and_FreeRADIUS_via_PAM.adoc doc/YubiKey_and_OpenVPN_via_PAM.adoc doc/YubiKey_and_Radius_via_PAM.adoc doc/YubiKey_and_SELinux.adoc doc/YubiKey_and_SSH_via_PAM.adoc 76 | EXTRA_DIST += $(MANSOURCES) 77 | EXTRA_DIST += tests/aux/ykval.pl tests/aux/ldap.pl tests/aux/authfile 78 | 79 | SUFFIXES = .1.txt .1 .8.txt .8 80 | .1.txt.1: 81 | $(A2X) -L --format=manpage -a revdate="Version $(VERSION)" $< 82 | 83 | .8.txt.8: 84 | $(A2X) -L --format=manpage -a revdate="Version $(VERSION)" $< 85 | 86 | 87 | endif 88 | 89 | if ENABLE_COV 90 | AM_CFLAGS += --coverage 91 | AM_LDFLAGS = --coverage 92 | 93 | cov-reset: 94 | rm -fr coverage 95 | find . -name "*.gcda" -exec rm {} \; 96 | lcov --directory . --zerocounters 97 | 98 | cov-report: 99 | mkdir -p coverage 100 | lcov --compat-libtool --directory . --capture --output-file coverage/app.info 101 | lcov --extract coverage/app.info '*.c' --output-file coverage/app2.info 102 | genhtml -o coverage/ coverage/app2.info 103 | 104 | cov: 105 | make cov-report 106 | 107 | clean-local: 108 | make cov-reset 109 | 110 | check: 111 | make cov 112 | endif 113 | 114 | if ENABLE_CPPCHECK 115 | cppcheck: 116 | $(CPPCHECK) -q -v -f --enable=all $(DEFS) . 117 | endif 118 | 119 | # Release 120 | 121 | ChangeLog: 122 | cd $(srcdir) && git2cl > ChangeLog 123 | 124 | check-doc-dist: 125 | perl -pe "s,^EXTRA_DIST = .*,EXTRA_DIST = `cd $(srcdir) && ls doc/*.adoc | xargs echo`," < $(srcdir)/Makefile.am > check-doc-dist.tmp 126 | diff -ur $(srcdir)/Makefile.am check-doc-dist.tmp || \ 127 | (rm -f check-doc-dist.tmp; echo 'error: please update $(srcdir)/Makefile.am to include all docs'; exit 1) 128 | rm -f check-doc-dist.tmp 129 | 130 | PROJECT = yubico-pam 131 | 132 | release: 133 | @if test -z "$(KEYID)"; then \ 134 | echo "Try this instead:"; \ 135 | echo " make release KEYID=[PGPKEYID]"; \ 136 | echo "For example:"; \ 137 | echo " make release KEYID=2117364A"; \ 138 | exit 1; \ 139 | fi 140 | @head -3 $(srcdir)/NEWS | grep -q "Version $(VERSION) .released `date -I`" || \ 141 | (echo 'error: Update date/version in $(srcdir)/NEWS.'; exit 1) 142 | @if test ! -d "$(YUBICO_WWW_REPO)"; then \ 143 | echo "WWW repo not found!"; \ 144 | echo "Make sure that YUBICO_WWW_REPO is set"; \ 145 | exit 1; \ 146 | fi 147 | rm -f ChangeLog 148 | make check-doc-dist ChangeLog distcheck 149 | gpg --detach-sign --default-key $(KEYID) $(PACKAGE)-$(VERSION).tar.gz 150 | gpg --verify $(PACKAGE)-$(VERSION).tar.gz.sig 151 | cd $(srcdir) && git push 152 | cd $(srcdir) && git tag -u $(KEYID) -m $(VERSION) $(VERSION) 153 | cd $(srcdir) && git push --tags 154 | $(YUBICO_WWW_REPO)/publish $(PROJECT) $(VERSION) $(PACKAGE)-$(VERSION).tar.gz* 155 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | pam_yubico NEWS -- History of user-visible changes. -*- outline -*- 2 | 3 | * Version 2.28 (unreleased) 4 | 5 | * Version 2.27 (released 2021-04-09) 6 | 7 | ** Add always_prompt configuration option. 8 | 9 | ** Add client certificate support for ldap. 10 | 11 | ** Add starttls support for ldap. 12 | 13 | ** Add ldap_bind_as_user support. 14 | 15 | ** Parsing, cleanliness and string fixes. 16 | 17 | ** Documentation and spelling fixes. 18 | 19 | * Version 2.26 (released 2018-04-20) 20 | 21 | ** Make sure to close authfile (CVE-2018-9275). 22 | 23 | ** Fix compiler warnings. 24 | 25 | ** Open file descriptors with O_CLOEXEC. 26 | 27 | ** Use mkostemp() instead of mkstemp(). 28 | 29 | * Version 2.25 (released 2018-03-27) 30 | 31 | ** Documentation updates. 32 | 33 | ** Only do OTP validation if it's a token that might be valid. 34 | 35 | ** Return early in case user has no valid tokens. 36 | 37 | ** Ldap, compare values only with yubi_attr attributes. 38 | 39 | ** Add nullok parameter. 40 | 41 | * Version 2.24 (released 2016-11-25) 42 | 43 | ** Debug mode changed, allows file output with debug_file. 44 | 45 | ** Fixup returning user-unknown correctly. 46 | 47 | * Version 2.23 (released 2016-06-15) 48 | 49 | ** Fix an issue where a failure to set permissions was wrongly outputted. 50 | 51 | * Version 2.22 (released 2016-05-23) 52 | 53 | ** Documentation improvements. 54 | 55 | ** Retain ownership and permission of challenge files (issue #92). 56 | 57 | ** Make dependency on yubico-c-client 2.15 clearer. 58 | 59 | * Version 2.21 (released 2016-02-19) 60 | 61 | ** Add proxy support for yubico-c-client. 62 | 63 | ** Check that conv is set before trying to use it 64 | fixes a crash bug with the osx loginwindow. 65 | 66 | ** Add building of a mac installer. 67 | 68 | * Version 2.20 (released 2015-09-22) 69 | 70 | ** Add cainfo option to allow usage of a cabundle instead of path. 71 | 72 | ** Support comments in authfile. 73 | 74 | ** For challenge response with system-wide directory, write the files as root 75 | instead of the user. 76 | 77 | * Version 2.19 (released 2015-03-23) 78 | 79 | ** Add new ldap functionality 80 | ldap_bind_user and ldap_bind_password for authenticated binds 81 | ldap_filter for using subtree search and a filter 82 | ldap_cacertfile to use a specific cacert for ldaps 83 | 84 | * Version 2.18 (released 2015-02-12) 85 | 86 | ** Fix a memory leak of the pam response data. 87 | 88 | ** Add more tests. 89 | 90 | ** Add version flag to ykpamcfg. 91 | 92 | * Version 2.17 (released 2014-08-26) 93 | 94 | ** Fix a bug with the 'urllist' parameter where urls would be forgotten. 95 | 96 | ** Manpages converted to asciidoc. 97 | 98 | * Version 2.16 (released 2014-06-10) 99 | 100 | ** Fix a crashbug with the new parameter 'urllist' 101 | 102 | * Version 2.15 (released 2014-04-30) 103 | 104 | ** Added new parameter 'urllist' 105 | 106 | ** Added pam_yubico(8) man page. 107 | 108 | ** Fix memory leak. 109 | 110 | ** Bump yubico-c-client version requirement to 2.12. 111 | 112 | * Version 2.14 (released 2013-09-27) 113 | 114 | ** Don't install internal header files. 115 | 116 | ** Don't print debug info when the "debug" parameter is not given. 117 | 118 | ** Use PBKDF2 to process expected reply for challenge-response mode. 119 | 120 | ** Fixup memory leaks and leaks of privilege. 121 | 122 | ** Let return values reflect whether the user wasn't found or other error. 123 | 124 | * Version 2.13 (released 2013-03-01) 125 | 126 | ** Fix a bug in the version check to support major version > 2 (neo). 127 | Patch from https://github.com/wwest4 128 | 129 | ** Give ykpamcfg an option for specifying path. 130 | 131 | * Version 2.12 (released 2012-06-15) 132 | 133 | ** Only use libyubikey when --with-cr is used. 134 | 135 | ** Set correct permissions on tempfile. 136 | 137 | ** YubiKey 2.2 contains a bug in challenge-response that makes it output the 138 | same response to all challenges unless HMAC_LT64 is set. Add warnings to 139 | ykpamcfg and a warning through conversate in the pam module. Keys programmed 140 | like this should be reprogrammed with the HMAC_LT64 flag set. 141 | 142 | * Version 2.11 (released 2012-02-10) 143 | 144 | ** Fix crash-bug with challenge-response mode when button press is required, 145 | but button is never pressed. Reported and fixed by 146 | Lingzhu Xiang . 147 | 148 | ** Fix a memset() with wrong size as reported by clang, as well as some 149 | other problems/warnings when building on Mac OS X, thanks to 150 | Clemens Lang . 151 | 152 | ** Add prefix-matching of LDAP fetched values, so you can store the 153 | token-to-user mapping in a multi-value attribute with values like 154 | "yubikey:publicid", "other-token:something" etc. Patch by 155 | Remi Mollon . 156 | 157 | * Version 2.10 (released 2011-12-14) 158 | 159 | ** Drop permissions (to the user that is trying to authenticate) before 160 | accessing files in the users home directory. Largely based on a patch by 161 | Ricky Zhou . Thanks! 162 | ** Restore challenge-response support - version 2.7 was supposed to make 163 | the dependency on libykpers optional, but in reality accidentally 164 | disabled challenge-response for all configurations. As before, use 165 | --without-cr to compile pam_yubico without the ykpers dependency. 166 | 167 | * Version 2.9 (released 2011-11-17) 168 | 169 | ** Security: Explicitly request ykclient to verify server signature. 170 | ykclient <= 2.5 strangely enough defaults to signing requests, but not 171 | verifying signatures in responses when it is supplied with a client key. 172 | 173 | Reported and patched by Dominic Rutherford . 174 | 175 | * Version 2.8 (released 2011-08-26) 176 | 177 | ** Fix big security hole: Authentication succeeded when no password 178 | was given, unless use_first_pass was being used. 179 | This is fatal if pam_yubico is considered 'sufficient' in the PAM 180 | configuration. 181 | 182 | Reported and patched by Nanakos Chrysostomos . 183 | 184 | * Version 2.7 (released 2011-06-07) 185 | 186 | ** Make dependency on libykpers optional. 187 | Use --without-cr to force it. Reported by Jussi Sallinen . 188 | 189 | * Version 2.6 (released 2011-04-11) 190 | 191 | ** This release includes lots of patches by members of our open 192 | source community. Thank you all! 193 | 194 | ** Add Challenge-Response mode for offline validation (requires 195 | YubiKey 2.2). Patch by Tollef Fog Heen. 196 | 197 | ** Eliminate all problems with pam_get_data by simply getting rid 198 | of that code completely. This seems to have caused problems for a lot 199 | of people. 200 | 201 | ** Numerous LDAP bug fixes and improvements, including community 202 | patches by judas.iscariote and maxsanna81@gmail.com. Change to 203 | LDAPv3, since v2 has been declared historic for a looong time. 204 | 205 | ** Support passing capath parameter to Yubico validation client. 206 | Patch by Remi Mollon. 207 | 208 | ** Support public id's longer/shorter than 6 bytes. Patch by 209 | fraser.scott@gmail.com. 210 | 211 | ** Convert documentation to Asciidoc format used in Github wiki. 212 | 213 | ** Try to never log passwords in debug logs. 214 | 215 | * Version 2.5 (released 2010-09-10) 216 | 217 | ** Wiki articles are now inclded in the archive. Same license as code. 218 | Reported by dmitrij.ledkov in Issue #30: 219 | . 220 | 221 | * Version 2.4 (released 2010-09-10) 222 | 223 | ** New keyword "verbose_otp" to allow displaying OTP characters. 224 | Contributed by qistoph reported in Issue #22: 225 | . 226 | 227 | ** Build with -DPAM_DEBUG so that debug file writing works. 228 | Reported by qistoph in Issue #20: 229 | . 230 | 231 | ** Make deprecated "ldapserver" work again. 232 | Reported by giovannibajo in Issue #27: 233 | . 234 | 235 | ** Fix segmentation fault on 64-bit systems. 236 | Reported by multiple people in Issue #11: 237 | . 238 | 239 | ** Don't crash on ^D at su prompt, or generally, on a NULL password value. 240 | 241 | * Version 2.3 (released 2010-04-14) 242 | 243 | ** New keyword "ldap_uri" added. 244 | This keyword is preferred over the old "ldapserver" keyword, and 245 | allows you to specify a complete LDAP URI instead of only the hostname 246 | of your LDAP server. Contributed by Zubrick. 247 | 248 | ** Improved README. 249 | Contributed by Erinn Looney-Triggs . 250 | 251 | * Version 2.2 (released 2009-05-11) 252 | 253 | ** Added new PAM configuration variable "key" for base64 client key. 254 | 255 | * Version 2.1 (released 2009-03-31) 256 | 257 | ** Fix documentation. 258 | 259 | ** Fix warning. 260 | 261 | * Version 2.0 (released 2009-03-25) 262 | 263 | ** Requires libykclient v2.0 or later. 264 | See . 265 | 266 | * Version 1.14 (released 2009-03-24) 267 | 268 | ** Quick release to sync release archive with svn code. 269 | 270 | * Version 1.13 (released 2009-03-24) 271 | 272 | ** Fix parsing of password into OTP/ID/password. 273 | Earlier string handling may have been incorrect for short strings. 274 | 275 | ** Don't pass integers via pam_set_data/pam_get_data. 276 | May solve problems on 64-bit platforms. Based on patch from 277 | forum.yubico.com. 278 | 279 | * Version 1.12 (released 2009-03-24) 280 | 281 | ** Add support for "use_first_pass" and "try_first_pass". 282 | They work similar to other PAM modules, see README for more 283 | documentation. 284 | 285 | Upgrade notice: If you are relying on getting the YubiKey OTP from an 286 | earlier PAM module, and no prompting by the pam_yubico module, you 287 | need to add "try_first_pass" to preserve the same behaviour. 288 | 289 | * Version 1.11 (released 2009-02-11) 290 | 291 | ** Added support to store user:keyid mapping in LDAP. 292 | 293 | * Version 1.10 (released 2009-01-13) 294 | 295 | ** Change license to 2-clause BSD. 296 | The Linux-PAM license is unclear, and in any case, the 2-clause BSD 297 | license is compatible with 3-clause BSD and GPL. 298 | 299 | * Version 1.9 (released 2009-01-13) 300 | 301 | ** Solaris portability improvements. 302 | Suggested by Martin Englund . 303 | 304 | * Version 1.8 (released 2008-09-15) 305 | 306 | ** Add new parameter 'url' to specify the server template URL. 307 | 308 | * Version 1.7 (released 2008-09-01) 309 | 310 | ** Support two-factor mode to provide a password. 311 | 312 | ** Support a user-specific configuration file to allow yubikeys per user. 313 | 314 | ** Use libyubikey-client instead of direct use of libcurl. 315 | 316 | ** Move *.m4's to m4/. 317 | 318 | * Version 1.6 (released 2008-01-11) 319 | 320 | ** First release from code.google.com repository. 321 | 322 | ** Clarify documentation with regard to license and development info. 323 | 324 | * Version 1.5 (internal release) 325 | 326 | ** Clarify that license is the same as Linux-PAM (GPLv2 or modified BSD). 327 | This is likely the last internal release, source moving to code.google.com. 328 | 329 | * Version 1.4 (internal release) 330 | 331 | ** Don't free CURL's user agent string before we're done. 332 | 333 | ** Version 1.3 (internal release) 334 | 335 | ** Disable echo'ing of password, for FreeRadius. 336 | 337 | * Version 1.2 (internal release) 338 | 339 | ** Added PDF/HTML manual, see yubico-pam.pdf and yubico-pam.html. 340 | 341 | ** Fixes to use new web service API. 342 | 343 | ** Add "url" parameter. 344 | 345 | ** Fix "alwaysok" parameter. 346 | 347 | ** Fix crash on empty server responses. 348 | 349 | ** Parse "status" properly. 350 | 351 | ** Better debug info. 352 | 353 | * Version 1.1 (internal release) 354 | 355 | ** Fix ws-api usage. 356 | 357 | ** Support "alwaysok". 358 | 359 | * Version 1.0 (internal release) 360 | 361 | ** Initial release. 362 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | == Yubico PAM module 2 | 3 | The Yubico PAM module provides an easy way to integrate the YubiKey 4 | into your existing user authentication infrastructure. PAM is used by 5 | GNU/Linux, Solaris and Mac OS X for user authentication, and by other 6 | specialized applications such as NCSA MyProxy. 7 | 8 | Status and Roadmap 9 | ------------------ 10 | 11 | The module is working for multi-user systems. The primary mode of 12 | operation is by doing online validation using a YubiKey validation 13 | service (such as the YubiCloud, or a private one configured using 14 | the 'urllist' parameter). 15 | 16 | In version 2.6, offline validation was also made possible through 17 | the use of HMAC-SHA1 Challenge-Response found in YubiKey 2.2 and 18 | later. This has introduced a dependency of libykpers-1 from the 19 | ykpersonalize package. Pass `--without-cr` to `configure` to avoid 20 | this dependency. 21 | 22 | The development community is co-ordinated via 23 | https://github.com/Yubico/yubico-pam[the GitHub project page]. 24 | 25 | The project is licensed under a BSD license. See the file COPYING for 26 | exact wording. For any copyright year range specified as YYYY-ZZZZ in 27 | this package note that the range specifies every single year in that 28 | closed interval. 29 | 30 | 31 | Building from Git 32 | ----------------- 33 | 34 | Skip to the next section if you are using an official packaged 35 | version. 36 | 37 | You may check out the sources using Git with the following command: 38 | 39 | ------ 40 | $ git clone https://github.com/Yubico/yubico-pam.git 41 | ------ 42 | 43 | This will create the directory `yubico-pam`. 44 | 45 | Autoconf, automake, libtool, asciidoc, xsltproc and docbook-xsl must be installed to create a 46 | compilable source tree. 47 | 48 | Generate the build system using: 49 | 50 | ------ 51 | $ cd yubico-pam 52 | $ autoreconf --install 53 | ------ 54 | 55 | 56 | === Building 57 | 58 | You will need to have https://developers.yubico.com/yubico-c-client[libykclient] 59 | (ykclient.h, libykclient.so) and libpam-dev (security/pam_appl.h, libpam.so) 60 | installed. It in turn requires cURL, which you need to have installed, and 61 | https://developers.yubico.com/yubico-c[libyubikey]. 62 | 63 | The Challenge-Response offline authentication requires libykpers-1 64 | from the 65 | https://developers.yubico.com/yubikey-personalization[yubikey-personalization] 66 | project: 67 | 68 | The selftests require perl with the module Net::LDAP::Server installed. 69 | 70 | The build system uses Autoconf, to set up the build system run: 71 | 72 | ./configure 73 | 74 | Use --without-ldap to disable ldap support. 75 | 76 | Then build the code, run the self-test and install the binaries: 77 | 78 | make check install 79 | 80 | 81 | == Installation 82 | 83 | === Fedora/EPEL 84 | 85 | There is already a package in Fedora/EPEL of yubico-pam that can be installed 86 | by using yum: 87 | 88 | $ sudo yum install pam_yubico 89 | 90 | 91 | === Ubuntu PPA 92 | 93 | There is an Ubuntu PPA (Personal Package Archive) for yubico-pam that 94 | can be installed using the following commands on reasonably modern 95 | Ubuntu platforms : 96 | 97 | $ sudo add-apt-repository ppa:yubico/stable 98 | $ sudo apt-get update 99 | $ sudo apt-get install libpam-yubico 100 | 101 | See the file `/usr/share/doc/libpam-yubico/README.Debian` after installing. 102 | 103 | 104 | === FreeBSD ports 105 | 106 | yubico-pam and the supporting Yubico packages have corresponding FreeBSD ports. To install: 107 | 108 | $ cd /usr/ports/security/pam_yubico 109 | $ make install clean 110 | 111 | Advanced configuration notes are available http://mjslabs.com/yubihow.html[here]. 112 | 113 | 114 | Configuration 115 | ------------- 116 | 117 | Install it in your PAM setup by adding a line to an appropriate file 118 | in `/etc/pam.d/`: 119 | 120 | ---- 121 | auth sufficient pam_yubico.so id=[Your API Client ID] debug 122 | ---- 123 | 124 | and move pam_yubico.so into /lib/security/ (or wherever PAM modules 125 | live in your system) : 126 | 127 | ---- 128 | mv /usr/local/lib/security/pam_yubico.so /lib/security/ 129 | ---- 130 | 131 | For more information, see the project documentation. 132 | 133 | Supported PAM module parameters are: 134 | 135 | [horizontal] 136 | authfile:: 137 | To indicate the location of the file that holds the 138 | mappings of YubiKey token IDs to user names. 139 | 140 | id:: 141 | Your API Client ID in the Yubico validation server. 142 | If you want to use the default YubiCloud service, 143 | go https://upgrade.yubico.com/getapikey[here]. 144 | 145 | key:: 146 | To indicate your client key in base64 format. 147 | The client key is also known as API key, and provides 148 | integrity in the communication between the client (you) 149 | and the validation server. 150 | If you want to get one for use with the default YubiCloud 151 | service, go https://upgrade.yubico.com/getapikey[here]. 152 | 153 | debug:: to enable debug output. 154 | 155 | debug_file:: filename to write debug to, file must exist and 156 | be a regular file. stdout is default. 157 | 158 | alwaysok:: 159 | to enable all authentication attempts to succeed 160 | (aka presentation mode). 161 | 162 | try_first_pass:: 163 | Before prompting the user for their password, the module 164 | first tries the previous stacked module´s password in case 165 | that satisfies this module as well. 166 | 167 | use_first_pass:: 168 | The argument use_first_pass forces the module to use a previous 169 | stacked modules password and will never prompt the user - if no 170 | password is available or the password is not appropriate, the user 171 | will be denied access. 172 | 173 | always_prompt:: 174 | If set, don't attempt to do a lookup to determine if the user has a 175 | YubiKey configured but instead prompt for one no matter what. This 176 | is useful in the case where ldap_bind_as_user is enabled but this 177 | module is being used to read the user's password (in a YubiKey+OTP 178 | auth scenario). 179 | 180 | nullok:: 181 | If set, don't fail when there are no tokens declared for the user 182 | in the authorization mapping files or in LDAP. 183 | This can be used to make YubiKey authentication optional unless 184 | the user has associated tokens. 185 | 186 | ldap_starttls:: 187 | If set, issue a STARTTLS command to the LDAP connection before 188 | attempting to bind to it. This is a common setup for servers 189 | that only listen on port 389 but still require TLS. 190 | 191 | ldap_bind_as_user:: 192 | If set, use the user logging in to bind to LDAP. This will use the 193 | password provided by the user via PAM. If this is set, ldapdn 194 | and uid_attr must also be set. Enabling this will cause 195 | 'ldap_bind_user' and 'ldap_bind_password' to be ignored 196 | 197 | urllist:: 198 | List of URL templates to be used. This is set by calling 199 | ykclient_set_url_bases. The list should be in the format : 200 | `https://server/wsapi/2.0/verify;https://server/wsapi/2.0/verify` 201 | 202 | url:: 203 | This option should not be used, please use the urllist 204 | option instead. 205 | Specify the URL template to use, this is set by calling 206 | yubikey_client_set_url_template, which defaults to: 207 | `https://api.yubico.com/wsapi/verify?id=%d&otp=%s` 208 | or 209 | `https://api.yubico.com/wsapi/2.0/verify?id=%d&otp=%s` 210 | depending on your version of yubico-c-client. 211 | 212 | capath:: 213 | specify the path where X509 certificates are stored. This is 214 | required if 'https' or 'ldaps' are used in 'url' and 'ldap_uri' 215 | respectively. 216 | 217 | cainfo:: 218 | Option to allow usage of a CA bundle instead of path. 219 | 220 | proxy:: 221 | specify a proxy to connect to the validation server. Valid schemes are 222 | http://, https://, socks4://, socks4a://, socks5:// or socks5h://. 223 | Socks5h asks the proxy to do the dns resolving. If no scheme or port is 224 | specified HTTP proxy port 1080 will be used. 225 | 226 | verbose_otp:: 227 | This argument is used to show the OTP (One-Time Password) when it 228 | is entered, i.e. to enable terminal echo of entered characters. 229 | You are advised to not use this, if you are using two factor 230 | authentication because that will display your password on the 231 | screen. 232 | This requires the service using the PAM module to 233 | display custom fields. This option can not be used with OpenSSH. 234 | 235 | ldap_uri:: specify the LDAP server URI (e.g. ldap://localhost). 236 | 237 | ldapserver:: 238 | specify the LDAP server host (default LDAP port is used). 239 | _Deprecated. Use "ldap_uri" instead._ 240 | 241 | ldapdn:: 242 | specify the dn where the users are stored 243 | (eg: ou=users,dc=domain,dc=com). 244 | 245 | ldap_clientcertfile:: 246 | The path to a client cert file to use when talking to the LDAP 247 | server. Note this requires 'ldap_clientkeyfile' to be set as well. 248 | 249 | ldap_clientkeyfile:: 250 | The path to a key to be used with the client cert when talking to 251 | the LDAP server. Note this requires 'ldap_clientcertfile' to be 252 | set as well. 253 | 254 | ldap_bind_user:: 255 | The user to attempt a LDAP bind as. 256 | 257 | ldap_bind_password:: 258 | The password to use on LDAP bind. 259 | 260 | ldap_filter:: 261 | An LDAP filter to use for attempting to find the correct object in LDAP. In this string `%u` will be replaced with the username. 262 | 263 | ldap_cacertfile:: 264 | CA certifcate file for the LDAP connection. 265 | 266 | user_attr:: specify the LDAP attribute used to store user names (eg:cn). 267 | 268 | yubi_attr:: specify the LDAP attribute used to store the YubiKey ID. 269 | 270 | yubi_attr_prefix:: 271 | specify the prefix of the LDAP attribute's value, in case 272 | of a generic attribute, used to store several types of IDs. 273 | 274 | token_id_length:: 275 | Length of ID prefixing the OTP (this is 12 if using the 276 | YubiCloud). 277 | 278 | mode:: 279 | Mode of operation. Use "client" for online validation with 280 | a YubiKey validation service such as the YubiCloud, or use 281 | "challenge-response" for offline validation using YubiKeys 282 | with HMAC-SHA-1 Challenge-Response configurations. See the 283 | man-page ykpamcfg(1) for further details on how to configure 284 | offline Challenge-Response validation. 285 | 286 | chalresp_path:: 287 | Directory that is used to store the challenge files in case of a system-wide 288 | configuration (in contrast to challenge files being stored in a user's home 289 | directory). This location should be only readable and writable by root. Refer 290 | to `Authentication_Using_Challenge-Response.adoc` for more information about 291 | such a setup. 292 | 293 | mysql_server:: 294 | Hostname/Adress of mysql server to use for mapping. 295 | 296 | mysql_port:: 297 | Network port of mysql server. 298 | 299 | mysql_user:: 300 | User for accessing the mysql database. 301 | 302 | mysql_password:: 303 | Password for the mysql user. 304 | 305 | mysql_database:: 306 | The mysql database to use. 307 | 308 | If you are using "debug" you may find it useful to create a 309 | world-writable log file: 310 | 311 | [source, sh] 312 | ---- 313 | touch /var/run/pam-debug.log 314 | chmod go+w /var/run/pam-debug.log 315 | ---- 316 | 317 | NOTE: Please remember, physical access to systems often allows the circumvention of security controls. If an attacker has physical access to your system (such as a laptop left in a hotel room) and can boot into single user mode, they can disable yubico-pam in your system configuration. 318 | 319 | 320 | Authorization Mapping Files 321 | --------------------------- 322 | A mapping must be made between the YubiKey token ID and the user ID it is 323 | attached to. There are two ways to do this, either centrally in one file, or 324 | individually, where users can create the mapping in their home directories. 325 | If the central authorization mapping file is being used, user home directory 326 | mappings will not be used and the opposite applies if user home directory 327 | mappings are being used, the central authorization mappings file will not 328 | be used. 329 | 330 | 331 | === Central authorization mapping 332 | Create a `/etc/yubikey_mappings`, the file must contain a user name and the 333 | YubiKey token ID separated by colons (same format as the passwd file) for 334 | each user you want to allow onto the system using a YubiKey. 335 | 336 | The mappings should look like this, one per line: 337 | 338 | :::…. 339 | :::…. 340 | 341 | Now add `authfile=/etc/yubikey_mappings` to your PAM configuration line, so it 342 | looks like: 343 | 344 | auth sufficient pam_yubico.so id=[Your API Client ID] authfile=/etc/yubikey_mappings 345 | 346 | 347 | === Individual authorization mapping by user 348 | Each user creates a `~/.yubico/authorized_yubikeys` file inside of their home 349 | directory and places the mapping in that file, the file must have only one 350 | line: 351 | 352 | 353 | :: 354 | 355 | 356 | This is much the same concept as the SSH authorized_keys file. 357 | 358 | 359 | Obtaining the YubiKey token ID (a.k.a. public ID) 360 | ------------------------------------------------- 361 | 362 | You can obtain the YubiKey token ID in several ways. One is by 363 | removing the last 32 characters of any OTP (One Time Password) 364 | generated with your YubiKey. Another is by using the 365 | http://demo.yubico.com/php-yubico/Modhex_Calculator.php[modhex calculator]. 366 | 367 | Enter your YubiKey OTP and convert it, your YubiKey token ID is 12 368 | characters and listed as: 369 | 370 | Modhex encoded: XXXXXXX 371 | 372 | Not sure what that last bit meant? Here is how to get a copy of your OTP. 373 | 374 | === Fast way 375 | . Open a terminal 376 | . Press the YubiKey's button 377 | It will output an OTP into the shell: 378 | + 379 | [source, sh] 380 | ------ 381 | $ cccccccgklgcvnkcvnnegrnhgrjkhlkfhdkclfncvlgj 382 | bash: cccccccgklgcvnkcvnnegrnhgrjkhlkfhdkclfncvlgj: command not found 383 | ------ 384 | + 385 | This can be pasted into the Modhex_Calculator page. 386 | 387 | === Harder way 388 | This requires you to have the pam module enabled with 'debug' turned on. When 389 | prompted for the YubiKey press the button. The pam module will print out debug 390 | information including the OTP and ID of your token to the shell -- copy the ID 391 | into your config file and you should be up and going. 392 | 393 | ------ 394 | YubiKey for `youruser': 395 | [pam_yubico.c:pam_sm_authenticate(867)] conv returned 44 bytes 396 | [pam_yubico.c:pam_sm_authenticate(885)] Skipping first 0 bytes. Length is 44, token_id set to 12 and token OTP always 32. 397 | [pam_yubico.c:pam_sm_authenticate(892)] OTP: ccccccclabcabkhbdncicglfltnukadfoifadfhhhhfe ID: cccccclabcab 398 | ------ 399 | 400 | 401 | Yubico PAM module and SELinux. 402 | ------------------------------ 403 | Users with SELinux in enforcing mode (the default on Fedora 17+) may experience 404 | login problems with services including those validated via 405 | polkit-agent-helper-1, sshd and login. 406 | 407 | This is https://bugzilla.redhat.com/show_bug.cgi?id=841693#c3[documented in Red Hat bugzilla] 408 | including a work around for ssh (Equivalent files could be created for 409 | other services). Systems in 'permissive' mode will generate AVC warnings but 410 | authentication will succeed. 411 | 412 | To determine if you have SELinux enforcing or not run the `sestatus` command. 413 | 414 | Examples 415 | -------- 416 | 417 | If you want to use the YubiKey to authenticate you on Linux console 418 | logins, add the following to the top of `/etc/pam.d/login`: 419 | 420 | auth sufficient pam_yubico.so id=[Your API Client ID] debug 421 | 422 | OpenVPN and ActiveDirectory 423 | --------------------------- 424 | See Michael Ludvig's sample Active Directory schema extensions for YubiKey public ID attribute storage / association with a particular user account: https://github.com/mludvig/yubikey-ldap/tree/master/microsoft-schema 425 | 426 | create file '/etc/pam.d/openvpn': 427 | 428 | auth required pam_yubico.so ldap_uri=ldap://contoso.com debug id=[Your API ID] yubi_attr=YubiKeyID 429 | ldapdn=DC=contoso,DC=com 430 | ldap_filter=(&(sAMAccountName=%u)(objectClass=user)(memberOf=CN=somegroup,DC=contoso,DC=com)) 431 | [ldap_bind_user=CN=binduser,OU=Service Accounts,DC=contoso,DC=com] ldap_bind_password=bind_password try_first_pass 432 | account required pam_yubico.so 433 | 434 | create file 'openvpn.conf' 435 | 436 | plugin openvpn-plugin-auth-pam.so openvpn 437 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | README -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # Written by Simon Josefsson . 2 | # Copyright (c) 2006-2014 Yubico AB 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are 7 | # met: 8 | # 9 | # * Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 12 | # * Redistributions in binary form must reproduce the above 13 | # copyright notice, this list of conditions and the following 14 | # disclaimer in the documentation and/or other materials provided 15 | # with the distribution. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | AC_INIT([pam_yubico], [2.28]) 30 | AC_CONFIG_AUX_DIR([build-aux]) 31 | AC_CONFIG_MACRO_DIR([m4]) 32 | AM_INIT_AUTOMAKE([1.11 foreign -Wall -Werror]) 33 | AM_SILENT_RULES([yes]) 34 | AM_PROG_CC_C_O 35 | 36 | m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) 37 | 38 | AC_LIBTOOL_WIN32_DLL 39 | AC_DISABLE_STATIC 40 | AC_PROG_LIBTOOL 41 | AM_MISSING_PROG([A2X], a2x, $missing_dir) 42 | 43 | AC_CHECK_HEADERS([security/pam_appl.h], [], 44 | [AC_MSG_ERROR([[PAM header files not found, install libpam-dev.]])]) 45 | AC_CHECK_HEADERS([security/pam_modules.h security/_pam_macros.h security/pam_modutil.h], [], [], 46 | [#include 47 | #include ]) 48 | 49 | AC_CHECK_LIB([pam], [pam_start], [AC_SUBST([LIBPAM], ["-lpam"])]) 50 | 51 | AC_SEARCH_LIBS([pam_modutil_drop_priv], ["pam"], [AC_DEFINE([HAVE_PAM_MODUTIL_DROP_PRIV], [1])]) 52 | 53 | # --disable-documentation 54 | AC_ARG_ENABLE([documentation], 55 | [AS_HELP_STRING([--disable-documentation], 56 | [do not build documentation])], 57 | [enable_doc="${enableval}" ], 58 | [enable_doc="yes"]) 59 | AM_CONDITIONAL(ENABLE_DOC, test "$enable_doc" != "no") 60 | 61 | AC_ARG_WITH([ldap], 62 | [AS_HELP_STRING([--without-ldap], 63 | [disable support for ldap])], 64 | [], 65 | [with_ldap=yes]) 66 | 67 | LIBLDAP= 68 | AS_IF([test "x$with_ldap" != xno], 69 | [AC_CHECK_LIB([ldap], [ldap_init], 70 | [AC_SUBST([LIBLDAP], ["-lldap -llber"]) 71 | AC_DEFINE([HAVE_LIBLDAP], [1], 72 | [Define if you have libldap]) 73 | ], 74 | [AC_MSG_WARN( 75 | [libldap not found, will not be compiled (--without-ldap to disable ldap support)])], 76 | [])]) 77 | 78 | AC_ARG_WITH([mysql], 79 | [AS_HELP_STRING([--without-mysql], 80 | [disable support for mysql])], 81 | [], 82 | [with_mysql=yes]) 83 | AS_IF([test "x$with_mysql" != xno], 84 | [ 85 | PKG_CHECK_MODULES([MYSQL], [mysqlclient], 86 | [AC_DEFINE([HAVE_MYSQL], [1],[Define if you have mysqlclient])], 87 | [AC_MSG_WARN( 88 | [libmysqlclient not found, will not be compiled (--without-mysql to disable mysql support)])]) 89 | ]) 90 | AM_CONDITIONAL(MYSQL_SUPPORT,test "x$with_mysql" != xno) 91 | 92 | AC_LIB_HAVE_LINKFLAGS([ykclient],, [#include ], 93 | [ykclient_set_proxy(0, 0)]) 94 | if test "$ac_cv_libykclient" != yes; then 95 | AC_MSG_ERROR([[Libykclient v2.15+ required, see https://developers.yubico.com/yubico-c-client/]]) 96 | fi 97 | 98 | AC_LIB_HAVE_LINKFLAGS(yubikey,, [#include ], 99 | [yubikey_modhex_p("foo")]) 100 | 101 | AC_ARG_WITH([cr], 102 | [AS_HELP_STRING([--without-cr], 103 | [disable support for challenge/response])], 104 | [], 105 | [with_cr=yes]) 106 | if test "x$with_cr" != xno; then 107 | PKG_CHECK_MODULES([YKPERS], [ykpers-1 >= 1.8.0]); 108 | # libyubikey required for HAVE_CR 109 | if test "$ac_cv_libyubikey" != yes; then 110 | AC_MSG_ERROR([Libyubikey v1.5+ not found, see https://developers.yubico.com/yubico-c/ (required for challenge-response)]) 111 | fi 112 | fi 113 | if test -n "$YKPERS_LIBS"; then 114 | AC_DEFINE([HAVE_CR], [1], [Define if you have libykpers-1 and libyubikey]) 115 | fi 116 | AM_CONDITIONAL([YKPERS], [test -n "$YKPERS_LIBS"]) 117 | 118 | AC_SUBST(PAMDIR, "\$(exec_prefix)/lib/security") 119 | AC_ARG_WITH(pam-dir, 120 | AC_HELP_STRING([--with-pam-dir=DIR], 121 | [Where to install PAM module [[PREFIX/lib/security]]]), 122 | [case "${withval}" in 123 | /*) PAMDIR="${withval}";; 124 | ./*|../*) AC_MSG_ERROR(Bad value for --with-pam-dir);; 125 | *) PAMDIR="\$(exec_prefix)/lib/${withval}";; 126 | esac]) 127 | AC_MSG_NOTICE([PAM installation path $PAMDIR]) 128 | 129 | AC_ARG_ENABLE([coverage], 130 | [AS_HELP_STRING([--enable-coverage], 131 | [use Gcov to test the test suite])], 132 | [], 133 | [enable_cov=no]) 134 | AM_CONDITIONAL([ENABLE_COV],[test '!' "$enable_cov" = no]) 135 | 136 | AC_ARG_ENABLE([cppcheck], 137 | [AS_HELP_STRING([--enable-cppcheck], 138 | [run cppcheck])], 139 | [enable_cppcheck="$enableval"], 140 | [enable_cppcheck="no"]) 141 | 142 | have_cppcheck=no 143 | AS_IF([test "x$enable_cppcheck" != xno], 144 | [AC_PATH_PROG([CPPCHECK], [cppcheck], [NONE]) 145 | AS_IF([test "x$enable_cppcheck" != xno], 146 | [have_cppcheck=yes 147 | AC_SUBST([CPPCHECK])], 148 | [have_cppcheck=no 149 | AS_IF([test "x$enable_cppcheck" != xauto], 150 | [AC_MSG_ERROR([cannot find cppcheck])])])]) 151 | AM_CONDITIONAL([ENABLE_CPPCHECK],[test '!' "$have_cppcheck" = no]) 152 | 153 | AC_ARG_ENABLE([gcc-warnings], 154 | [AS_HELP_STRING([--enable-gcc-warnings], 155 | [turn on lots of GCC warnings (for developers)])], 156 | [case $enableval in 157 | yes|no) ;; 158 | *) AC_MSG_ERROR([bad value $enableval for gcc-warnings option]) ;; 159 | esac 160 | gl_gcc_warnings=$enableval], 161 | [gl_gcc_warnings=no] 162 | ) 163 | 164 | if test "$gl_gcc_warnings" = yes; then 165 | nw="$nw -Wsystem-headers" # Don't let system headers trigger warnings 166 | nw="$nw -Wpadded" # Struct's arenot padded 167 | nw="$nw -Wc++-compat" # We don't care strongly about C++ compilers 168 | nw="$nw -Wtraditional" # Warns on #elif which we use often 169 | nw="$nw -Wtraditional-conversion" # Too many warnings for now 170 | nw="$nw -Wconversion" # Too many warnings for now 171 | nw="$nw -Wsuggest-attribute=pure" # Is it worth using attributes? 172 | nw="$nw -Woverlength-strings" # Don't warn on long strings 173 | 174 | gl_MANYWARN_ALL_GCC([ws]) 175 | gl_MANYWARN_COMPLEMENT(ws, [$ws], [$nw]) 176 | for w in $ws; do 177 | gl_WARN_ADD([$w]) 178 | done 179 | 180 | gl_WARN_ADD([-fdiagnostics-show-option]) 181 | fi 182 | 183 | # Enable more secure memset if available 184 | AC_CHECK_FUNCS([memset_s explicit_bzero explicit_memset]) 185 | AC_MSG_CHECKING(whether we can use inline asm code) 186 | AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], 187 | [[ 188 | int a = 42; 189 | int *pnt = &a; 190 | __asm__ __volatile__ ("" : : "r"(pnt) : "memory"); 191 | ]])], 192 | [AC_MSG_RESULT(yes) 193 | AC_DEFINE([HAVE_INLINE_ASM], [1], [inline asm code can be used])] 194 | [AC_MSG_RESULT(no)] 195 | ) 196 | 197 | AC_CONFIG_FILES(Makefile) 198 | AC_CONFIG_FILES(tests/Makefile) 199 | AC_OUTPUT 200 | 201 | AC_MSG_NOTICE([Summary of build options: 202 | 203 | Version: ${VERSION} 204 | Host type: ${host} 205 | Install prefix: ${prefix} 206 | Compiler: ${CC} 207 | Library types: Shared=${enable_shared}, Static=${enable_static} 208 | LDAP: ${with_ldap} 209 | Challenge-Response: ${with_cr} 210 | ]) 211 | -------------------------------------------------------------------------------- /doc/Authentication_Using_Challenge-Response.adoc: -------------------------------------------------------------------------------- 1 | Local Authentication Using Challenge Response 2 | --------------------------------------------- 3 | 4 | The PAM module can utilize the HMAC-SHA1 Challenge-Response mode found 5 | in YubiKeys starting with version 2.2 for offline authentication. 6 | This mode is useful if you don't have a stable network connection to 7 | the YubiCloud. 8 | 9 | The ykpamcfg utility currently outputs the state information to a file 10 | in the current user's home directory (`$HOME/.yubico/challenge-123456` 11 | for a YubiKey with serial number API readout enabled, and 12 | `$HOME/.yubico/challenge` for one without). 13 | 14 | The PAM module supports a system-wide directory for these state files 15 | (in case the user's home directories are encrypted), but in a system-wide 16 | directory, the 'challenge' part should be replaced with the 17 | username. Example: `/var/yubico/alice-123456`. 18 | 19 | To use the system-wide mode, you currently have to move the generated 20 | state files manually and configure the PAM module accordingly. 21 | 22 | The following process is tested on Ubuntu 12.04. 23 | 24 | First install the package: 25 | 26 | ------ 27 | $ sudo apt-get install libpam-yubico 28 | ------ 29 | 30 | You will get a question about the PAM configuration line. Enter this 31 | line: 32 | 33 | ------ 34 | mode=challenge-response 35 | ------ 36 | 37 | The next question will be about which PAM modules to enable. Don't 38 | enable anything just yet, because you need to program your YubiKey 39 | first. 40 | 41 | If you have already installed the package or want to reconfigure it, 42 | you may use this command: 43 | 44 | ------ 45 | $ sudo dpkg-reconfigure libpam-yubico 46 | ------ 47 | 48 | The next step is to add a challenge-response slot to your YubiKey. If 49 | you have a normal YubiKey with OTP functionality on the first slot, 50 | you could add Challenge-Response on the second slot. You could have 51 | CR on the first slot, if you want. 52 | 53 | First, program a YubiKey for challenge response on Slot 2: 54 | 55 | ------ 56 | $ ykpersonalize -2 -ochal-resp -ochal-hmac -ohmac-lt64 -oserial-api-visible 57 | ... 58 | Commit? (y/n) [n]: y 59 | $ 60 | ------ 61 | 62 | Now, set the current user to require this YubiKey for logon: 63 | 64 | ------ 65 | $ mkdir $HOME/.yubico 66 | $ ykpamcfg -2 -v 67 | ... 68 | Stored initial challenge and expected response in '/home/alice/.yubico/challenge-123456'. 69 | $ 70 | ------ 71 | From security perspective, it is generally a good idea to move the challenge file in a system-wide path that is only read- and writable by root. To do this do as follow: 72 | 73 | ---- 74 | $ sudo mkdir /var/yubico 75 | $ sudo chown root.root /var/yubico 76 | $ sudo chmod 700 /var/yubico 77 | $ ykpamcfg -2 -v 78 | ... 79 | Stored initial challenge and expected response in '$HOME/.yubico/challenge-123456'. 80 | $ sudo mv ~/.yubico/challenge-123456 /var/yubico/alice-123456 81 | $ sudo chown root.root /var/yubico/alice-123456 82 | $ sudo chmod 600 /var/yubico/alice-123456 83 | ---- 84 | 85 | It is important that the file is named with the name of the user that is going to be authenticated by this YubiKey. 86 | 87 | Finally we tell the pam module where to look for the challenge file 88 | 89 | $ emacs /etc/pam.d/common-auth 90 | 91 | and edit the following line as follow: 92 | 93 | auth required pam_yubico.so mode=challenge-response chalresp_path=/var/yubico 94 | 95 | Then back to the PAM configuration step, first make sure you have a 96 | root terminal available to be able to disable YubiKey login in case of 97 | issues. 98 | 99 | $ sudo -s 100 | 101 | Then run the "pam-auth-update" command and enable the Yubico PAM 102 | module. 103 | 104 | $ sudo pam-auth-update 105 | 106 | You should now be able to authenticate using YubiKey 107 | Challenge-Reseponse together with a password like this: 108 | 109 | ---- 110 | jas@latte:~$ sudo -s 111 | [sudo] password for jas: 112 | root@latte:~# 113 | ---- 114 | 115 | Now remove the YubiKey and try again (in a new terminal to avoid sudo 116 | caching), and you should not be able to login. 117 | 118 | For debugging, you can make the PAM configuration line: 119 | 120 | mode=challenge-response debug 121 | 122 | and then create a log file: 123 | 124 | ---- 125 | # touch /var/run/pam-debug.log 126 | # chmod go+w /var/run/pam-debug.log 127 | ---- 128 | 129 | and then tail the file. For successful logins it should print 130 | something like this: 131 | 132 | ---- 133 | [pam_yubico.c:parse_cfg(721)] called. 134 | [pam_yubico.c:parse_cfg(722)] flags 32768 argc 2 135 | [pam_yubico.c:parse_cfg(724)] argv[0]=mode=challenge-response 136 | [pam_yubico.c:parse_cfg(724)] argv[1]=debug 137 | [pam_yubico.c:parse_cfg(725)] id=-1 138 | [pam_yubico.c:parse_cfg(726)] key=(null) 139 | [pam_yubico.c:parse_cfg(727)] debug=1 140 | [pam_yubico.c:parse_cfg(728)] alwaysok=0 141 | [pam_yubico.c:parse_cfg(729)] verbose_otp=0 142 | [pam_yubico.c:parse_cfg(730)] try_first_pass=0 143 | [pam_yubico.c:parse_cfg(731)] use_first_pass=0 144 | [pam_yubico.c:parse_cfg(732)] authfile=(null) 145 | [pam_yubico.c:parse_cfg(733)] ldapserver=(null) 146 | [pam_yubico.c:parse_cfg(734)] ldap_uri=(null) 147 | [pam_yubico.c:parse_cfg(735)] ldapdn=(null) 148 | [pam_yubico.c:parse_cfg(736)] user_attr=(null) 149 | [pam_yubico.c:parse_cfg(737)] yubi_attr=(null) 150 | [pam_yubico.c:parse_cfg(738)] yubi_attr_prefix=(null) 151 | [pam_yubico.c:parse_cfg(739)] url=(null) 152 | [pam_yubico.c:parse_cfg(740)] capath=(null) 153 | [pam_yubico.c:parse_cfg(741)] token_id_length=12 154 | [pam_yubico.c:parse_cfg(742)] mode=chresp 155 | [pam_yubico.c:parse_cfg(743)] chalresp_path=(null) 156 | [pam_yubico.c:pam_sm_authenticate(775)] get user returned: jas 157 | [pam_yubico.c:do_challenge_response(493)] Loading challenge from file /home/jas/.yubico/challenge-1077187 158 | [util.c:load_chalresp_state(269)] Challenge: 23001a190724abf46c8022b008ccb65673dd634ecb150613771ec87f37850284d80dd5f8c8e56affb6da2e952b16682160e7f3ac4f816b64126bd9556e5be1, response: 63d4a679ed15335ffd4253e7609963bcdb0834d4, slot: 2 159 | [pam_yubico.c:do_challenge_response(566)] Got the expected response, generating new challenge (63 bytes). 160 | [pam_yubico.c:do_challenge_response(629)] Challenge-response success! 161 | ---- 162 | 163 | and if there is no YubiKey in the machine it will look like this: 164 | 165 | ---- 166 | [pam_yubico.c:parse_cfg(721)] called. 167 | [pam_yubico.c:parse_cfg(722)] flags 32768 argc 2 168 | [pam_yubico.c:parse_cfg(724)] argv[0]=mode=challenge-response 169 | [pam_yubico.c:parse_cfg(724)] argv[1]=debug 170 | [pam_yubico.c:parse_cfg(725)] id=-1 171 | [pam_yubico.c:parse_cfg(726)] key=(null) 172 | [pam_yubico.c:parse_cfg(727)] debug=1 173 | [pam_yubico.c:parse_cfg(728)] alwaysok=0 174 | [pam_yubico.c:parse_cfg(729)] verbose_otp=0 175 | [pam_yubico.c:parse_cfg(730)] try_first_pass=0 176 | [pam_yubico.c:parse_cfg(731)] use_first_pass=0 177 | [pam_yubico.c:parse_cfg(732)] authfile=(null) 178 | [pam_yubico.c:parse_cfg(733)] ldapserver=(null) 179 | [pam_yubico.c:parse_cfg(734)] ldap_uri=(null) 180 | [pam_yubico.c:parse_cfg(735)] ldapdn=(null) 181 | [pam_yubico.c:parse_cfg(736)] user_attr=(null) 182 | [pam_yubico.c:parse_cfg(737)] yubi_attr=(null) 183 | [pam_yubico.c:parse_cfg(738)] yubi_attr_prefix=(null) 184 | [pam_yubico.c:parse_cfg(739)] url=(null) 185 | [pam_yubico.c:parse_cfg(740)] capath=(null) 186 | [pam_yubico.c:parse_cfg(741)] token_id_length=12 187 | [pam_yubico.c:parse_cfg(742)] mode=chresp 188 | [pam_yubico.c:parse_cfg(743)] chalresp_path=(null) 189 | [pam_yubico.c:pam_sm_authenticate(775)] get user returned: jas 190 | [pam_yubico.c:do_challenge_response(478)] Failed initializing YubiKey 191 | [pam_yubico.c:do_challenge_response(640)] YubiKey core error: no YubiKey present 192 | ---- 193 | -------------------------------------------------------------------------------- /doc/MacOS_X_Challenge-Response.adoc: -------------------------------------------------------------------------------- 1 | == Setting up your YubiKey for challenge response authentication on Max OS X == 2 | 3 | This article explains the process to get the challenge-response 4 | authentication possible with newer YubiKeys working on Mac OS X. Since 5 | Mac OS X uses PAM like most other Unix/POSIX systems do, most of this 6 | should apply to other operating systems, too. 7 | 8 | === Getting yubico-pam === 9 | 10 | First you will have to install yubico-pam and its dependencies 11 | required for challenge-response authentication. Use your 12 | distribution's package manager to get it, or build from source. If 13 | you're on OS X you can use http://www.macports.org[MacPorts] to 14 | install yubico-pam: 15 | 16 | sudo port install yubico-pam 17 | 18 | NOTE: This will probably not work in non-superuser installations 19 | of MacPorts, because it needs to place the yubico PAM module into 20 | `/usr/lib/pam`. 21 | 22 | === Configuring your YubiKey === 23 | 24 | The next step would be to set up your YubiKey for challenge-response 25 | authentication, if you haven't done so already. Although this is 26 | possible with the command line `ykpersonalize` tool, the GUI "YubiKey 27 | Personalization Tool" is a more comfortable way to do this. 28 | 29 | 1. Plug in your YubiKey and start the YubiKey Personalization Tool 30 | + 31 | NOTE: YubiKey Personalization Tool shows whether your YubiKey supports challenge-response in the lower right. 32 | 2. Click 'Challenge-Response' 33 | 3. Select HMAC-SHA1 mode. Apparently Yubico-OTP mode doesn't work with yubico-pam at the moment. 34 | 4. Select the configuration slot you want to use 35 | (this text assumes slot two, but it should be easy enough to adapt the instructions if you prefer slot 1) 36 | 5. Select whether you want to require pressing the button for authentication 37 | + 38 | NOTE: If you enable this, you will have to press the button twice for each authentication with yubico-pam. This is because the PAM module does not only send the challenge on file and checks whether the response matches, but also generates a new challenge-response pair on success. 39 | 6. Use 'Variable input' as HMAC-SHA1 mode 40 | + 41 | WARNING: Using 'Fixed 64 byte input' for this value made my YubiKey always return the same response regardless of what the challenge was. Since this defies the purpose of challenge-response think twice and test before you use this! 42 | 7. Generate a secret key 43 | You won't need this key again, it's sufficient to have it on your YubiKey. Note that the YubiKey Personalization Tool by default logs the key to configuration_log.csv in your home directory. Consider turning this off in the settings before writing or shredding the file after writing. 44 | 8. Click 'Write Configuration' 45 | 46 | === Configuring your user account to accept the YubiKey === 47 | 48 | After setting up your YubiKey you need to configure your account to 49 | accept this YubiKey for authentication. To do this, open a terminal 50 | and run 51 | 52 | # create the directory where ykpamcfg will store the initial challenge 53 | mkdir -m0700 -p ~/.yubico 54 | # get the initial challenge from the YubiKey 55 | ykpamcfg -2 56 | 57 | If you used slot 1 above, replace -2 with -1. If you configured your 58 | YubiKey to require a button press the LED on the YubiKey will start 59 | blinking; press the button to send a challenge-response 60 | response. `ykpamcfg` should finish successfully telling you that it 61 | stored the initial challenge somewhere inside your home directory: 62 | 63 | ---- 64 | Stored initial challenge and expected response in '/path/to/your/home/.yubico/challenge-KEYID'. 65 | ---- 66 | 67 | This step will create a file with a challenge and the expected 68 | response (that can only be generated with the secret 69 | key footnote:[This is also the reason why you should avoid having copies of the key in other places than your YubiKey!] ) 70 | in your home directory. The PAM module will later open this file, read the 71 | challenge, send it to the connected YubiKey and check whether its 72 | answer matches the one on file. If it does, it generates a new 73 | challenge, asks the YubiKey for the correct response for this 74 | challenge and writes both into the file. This also means that you need 75 | to keep this file secure from other users (which is why we created the 76 | .yubico directory in your home with mode 0700). 77 | 78 | === Configuring your system to use Yubico PAM for authentication === 79 | 80 | Linux, Solaris, OS X and most BSD variants use the 81 | http://en.wikipedia.org/wiki/Pluggable_Authentication_Modules[Pluggable 82 | Authentication Modules] (PAM) framework to handle authentication. 83 | Using PAM you can specify which 84 | modules are used for authentication of users and which of them are 85 | required, optional and/or sufficient to authenticate a user. Using PAM 86 | you can for example set up multiple-factor authentication, by chaining 87 | multiple required modules. 88 | 89 | PAM is configured through files in `/etc/pam.d` on most systems. Each 90 | file in this directory is used for a specific service, i.e. the file 91 | `/etc/pam.d/sudo` is used to authenticate users for the `sudo` 92 | program. Debian, for example, uses include directives in these files 93 | to have a central place to configure authentication; in this case we 94 | are not using this on purpose, because challenge-response 95 | authentication doesn't work remotely (e.g. via SSH), so we only want 96 | to configure it for services we use when on site. 97 | 98 | The file format in these files is documented in `man 5 pam.conf`; it 99 | looks like this: 100 | 101 | function-class control-flag module-path arguments 102 | 103 | where 104 | 105 | [horizontal] 106 | *function-class*:: is one of `auth`, `account`, `session`, and 107 | `password`. Since we only care about authentication with the YubiKey 108 | and yubico-pam only handles authentication, we will always be using 109 | `auth` here. 110 | 111 | *control-flag*:: is one of `required`, `sufficient`, `optional` and 112 | some other values depending on your PAM implementation. If we want 113 | to make YubiKey challenge-response mandatory but combined with other 114 | methods (e.g. password), we can use `required`, if we want 115 | successful challenge-response to be enough to authenticate a user, 116 | we can use `sufficient`. `optional` is not of any use for us 117 | in this case. 118 | 119 | *module-path*:: selects the module to be used for this authentication 120 | step. This is used as filename in a directory where pam libraries 121 | are expected, on OS X e.g. `/usr/lib/pam`, `/usr/lib/security` on 122 | some other systems. We want `pam_yubico.so` in this case, which will 123 | load `/usr/lib/pam/pam_yubico.so`. 124 | 125 | *arguments*:: are passed to the pam module and can be used to 126 | configure its behavior. See 'Supported PAM module parameters' in 127 | https://github.com/Yubico/yubico-pam/blob/master/README[README] 128 | for a list of possible values. Since we want to use 129 | challenge-response, we add `mode=challenge-response` and to debug 130 | the setup initially also `debug`, separated by spaces. `debug` can 131 | safely be removed later. 132 | 133 | 134 | WARNING: If you misconfigure your PAM modules here you might lose 135 | your ability to sudo! Always keep a root shell open to be able to 136 | revert your changes in case something goes wrong! 137 | 138 | So, if we wanted to use the YubiKey to allow us to sudo without typing 139 | a password, we would add 140 | 141 | ---- 142 | auth sufficient pam_yubico.so mode=challenge-response debug 143 | ---- 144 | 145 | To get this working on the loginwindow for local interactive login add 146 | the `pam_yubico.so` to the `pam.d` file authorization as the first 147 | line. The whole file might look something like this (example taken 148 | from OS X): 149 | 150 | ---- 151 | # sudo: auth account password session 152 | auth sufficient pam_yubico.so mode=challenge-response debug 153 | auth required pam_opendirectory.so 154 | account required pam_permit.so 155 | password required pam_deny.so 156 | session required pam_permit.so 157 | ---- 158 | 159 | If we wanted to require successful challenge-response authentication 160 | in addition to the usual password, we can change the `sufficient` in 161 | the line we added to `required`. 162 | 163 | NOTE: In theory you can configure pretty much any service you use 164 | locally to use challenge-response authentication. In practice, I had 165 | problems configuring challenge-response into the login window of OS 166 | X. Keep a rescue disk or a remote root terminal available when 167 | attempting such configurations, just in case something goes wrong 168 | and you need to restore the PAM configuration to an old state. 169 | 170 | NOTE: On Debian it started working for me after accidentally 171 | getting the file-rights correctly. `755` for `~/.yubico` & `600` for 172 | the files therein. Otherwise the module can't find, read and/or 173 | write to the appropriate files. Your clue is the following debug 174 | messages. 175 | 176 | ---- 177 | [drop_privs.c:restore_privileges(128)] pam_modutil_drop_priv: -1 178 | [pam_yubico.c:do_challenge_response(542)] could not restore privileges 179 | [pam_yubico.c:do_challenge_response(664)] Challenge response failed: No such file or directory 180 | ---- 181 | -------------------------------------------------------------------------------- /doc/Two_Factor_PAM_Configuration.adoc: -------------------------------------------------------------------------------- 1 | PAM configuration is somewhat complex, but a typical use-case is to 2 | require both a password and YubiKey to allow access. This can be 3 | achieved by a PAM configuration like this: 4 | 5 | ---- 6 | auth requisite pam_yubico.so id=42 7 | auth required pam_unix.so use_first_pass 8 | ---- 9 | 10 | The first line makes pam_yubico check the OTP. Use either a per-user 11 | file called `~/.yubico/authorized_yubikeys`, or a system-wide file called 12 | `/etc/yubikey_mappings` to specify which YubiKeys that can be used to log 13 | in as specific users. See https://developers.yubico.com/yubico-pam[the README] 14 | for more information. 15 | 16 | The 'use_first_pass' on the next line says that the password the pam_unix 17 | module should check should be received from the earlier PAM modules 18 | and that the module should not query for passwords. 19 | 20 | Of course, if you use username/password verification from a SQL 21 | database or LDAP, you need to change the second line above. But the 22 | module you use needs to support 'use_first_pass' for this to work. 23 | Most modules support this. 24 | 25 | Be sure to comment out any other 'auth' lines in your PAM configuration, 26 | unless you want those. For example, Debian contains a 27 | '@include common-auth' which would confuse the configuration. 28 | 29 | To log in, you now need to enter both your Unix password and enter an 30 | OTP using your YubiKey. When prompted for the password, enter the Unix 31 | password first and then (without pressing enter) push the button on your 32 | YubiKey. 33 | 34 | If it doesn't work, enable debugging (see https://developers.yubico.com/yubico-pam[the README]) and try again. 35 | -------------------------------------------------------------------------------- /doc/Ubuntu_FreeRadius_YubiKey.adoc: -------------------------------------------------------------------------------- 1 | Ubuntu FreeRadius YubiKey 2 | ------------------------- 3 | 4 | Create and login to a fresh Ubuntu 10.04 LTS machine: 5 | 6 | ------ 7 | vmbuilder kvm ubuntu \ 8 | --dest /var/lib/libvirt/images/freeradius \ 9 | --proxy http://192.168.1.2/ubuntu \ 10 | --rootsize 10000 \ 11 | --mem 600 \ 12 | --suite lucid \ 13 | --flavour virtual \ 14 | --addpkg unattended-upgrades \ 15 | --addpkg openssh-server \ 16 | --addpkg avahi-daemon \ 17 | --addpkg acpid \ 18 | --ssh-key /root/.ssh/authorized_keys \ 19 | --libvirt qemu:///system \ 20 | --hostname freeradius \ 21 | --bridge br0 \ 22 | --debug 23 | ssh -l root freeradius.local 24 | ------ 25 | 26 | Install and configure software : 27 | -------------------------------- 28 | 29 | ------ 30 | apt-get install build-essential wget 31 | apt-get install libpam0g-dev libykclient3 libykclient-dev 32 | ------ 33 | 34 | Install PAM module: 35 | 36 | ------ 37 | wget http://yubico-pam.googlecode.com/files/pam_yubico-2.4.tar.gz 38 | tar xfz pam_yubico-2.4.tar.gz 39 | cd pam_yubico-2.4 40 | ./configure 41 | make check install 42 | ln -s /usr/local/lib/security/pam_yubico.so /lib/security/ 43 | ------ 44 | 45 | Setup PAM debug log file: 46 | 47 | ------ 48 | touch /var/run/pam-debug.log 49 | chmod go+w /var/run/pam-debug.log 50 | tail -F /var/run/pam-debug.log & 51 | ------ 52 | 53 | Install FreeRadius: 54 | 55 | ------ 56 | apt-get install freeradius 57 | /etc/init.d/freeradius stop 58 | ------ 59 | 60 | Next we configure FreeRadius. First add this to /etc/freeradius/users: 61 | 62 | ------ 63 | DEFAULT Auth-Type = pam 64 | ------ 65 | 66 | Then comment out 'pap' and uncomment 'pam' from 67 | /etc/freeradius/sites-available/default. 68 | 69 | Add to the top of /etc/pam.d/radiusd: 70 | 71 | ------ 72 | auth sufficient pam_yubico.so id=1 debug authfile=/etc/yubikey_mapping 73 | ------ 74 | 75 | If you want to use HMAC signing, specify the 'key=' field too, like this: 76 | 77 | ------ 78 | auth sufficient pam_yubico.so id=1 key=b64foo debug authfile=/etc/yubikey_mapping 79 | ------ 80 | 81 | Create a file /etc/yubikey_mapping (ccccccccltnc is Alice's YubiKey's public ID) : 82 | 83 | ------ 84 | alice:ccccccccltnc 85 | ------ 86 | 87 | Create a Unix account 'alice': XXX should not be necessary? 88 | 89 | ------ 90 | adduser --disabled-password alice 91 | ------ 92 | 93 | Just press RET and finally 'y RET' on the prompts. 94 | 95 | Start radiusd: 96 | 97 | ------ 98 | LD_PRELOAD=/lib/libpam.so.0 freeradius -X 99 | ------ 100 | 101 | 102 | Testing authentication : 103 | ------------------------ 104 | 105 | Confirm that it works with radtest (use a real OTP from Alice's YubiKey) : 106 | 107 | ------ 108 | radtest alice ccccccccltncdjjifceergtnukivgiujhgehgnkrfcef 127.0.0.1 0 testing123 109 | ------ 110 | 111 | Output should be like this: 112 | 113 | ------ 114 | Sending Access-Request of id 69 to 127.0.0.1 port 1812 115 | User-Name = "alice" 116 | User-Password = "ccccccccltncdjjifceergtnukivgiujhgehgnkrfcef" 117 | NAS-IP-Address = 127.0.1.1 118 | NAS-Port = 0 119 | rad_recv: Access-Accept packet from host 127.0.0.1 port 1812, id=69, length=20 120 | ------ 121 | 122 | PAM debug output should be like this: 123 | 124 | ------ 125 | [pam_yubico.c:parse_cfg(404)] called. 126 | [pam_yubico.c:parse_cfg(405)] flags 0 argc 3 127 | [pam_yubico.c:parse_cfg(407)] argv[0]=id=1 128 | [pam_yubico.c:parse_cfg(407)] argv[1]=debug 129 | [pam_yubico.c:parse_cfg(407)] argv[2]=authfile=/etc/yubikey_mapping 130 | [pam_yubico.c:parse_cfg(408)] id=1 131 | [pam_yubico.c:parse_cfg(409)] key=(null) 132 | [pam_yubico.c:parse_cfg(410)] debug=1 133 | [pam_yubico.c:parse_cfg(411)] alwaysok=0 134 | [pam_yubico.c:parse_cfg(412)] verbose_otp=0 135 | [pam_yubico.c:parse_cfg(413)] try_first_pass=0 136 | [pam_yubico.c:parse_cfg(414)] use_first_pass=0 137 | [pam_yubico.c:parse_cfg(415)] authfile=/etc/yubikey_mapping 138 | [pam_yubico.c:parse_cfg(416)] ldapserver=(null) 139 | [pam_yubico.c:parse_cfg(417)] ldap_uri=(null) 140 | [pam_yubico.c:parse_cfg(418)] ldapdn=(null) 141 | [pam_yubico.c:parse_cfg(419)] user_attr=(null) 142 | [pam_yubico.c:parse_cfg(420)] yubi_attr=(null) 143 | [pam_yubico.c:pam_sm_authenticate(452)] get user returned: alice 144 | [pam_yubico.c:pam_sm_authenticate(542)] conv returned: ccccccccltncdjjifceergtnukivgiujhgehgnkrfcef 145 | [pam_yubico.c:pam_sm_authenticate(558)] OTP: ccccccccltncdjjifceergtnukivgiujhgehgnkrfcef ID: ccccccccltnc 146 | [pam_yubico.c:pam_sm_authenticate(583)] ykclient return value (0): Success 147 | [pam_yubico.c:check_user_token(117)] Authorization line: alice:ccccccccltnc 148 | [pam_yubico.c:check_user_token(121)] Matched user: alice 149 | [pam_yubico.c:check_user_token(125)] Authorization token: ccccccccltnc 150 | [pam_yubico.c:check_user_token(128)] Match user/token as alice/ccccccccltnc 151 | [pam_yubico.c:pam_sm_authenticate(625)] done. [Success] 152 | ------ 153 | 154 | FreeRadius debug output should be like this: 155 | 156 | ------ 157 | rad_recv: Access-Request packet from host 127.0.0.1 port 38575, id=69, length=89 158 | User-Name = "alice" 159 | User-Password = "ccccccccltncdjjifceergtnukivgiujhgehgnkrfcef" 160 | NAS-IP-Address = 127.0.1.1 161 | NAS-Port = 0 162 | +- entering group authorize {...} 163 | ++[preprocess] returns ok 164 | ++[chap] returns noop 165 | ++[mschap] returns noop 166 | [suffix] No '@' in User-Name = "alice", looking up realm NULL 167 | [suffix] No such realm "NULL" 168 | ++[suffix] returns noop 169 | [eap] No EAP-Message, not doing EAP 170 | ++[eap] returns noop 171 | [files] users: Matched entry DEFAULT at line 204 172 | ++[files] returns ok 173 | ++[expiration] returns noop 174 | ++[logintime] returns noop 175 | Found Auth-Type = PAM 176 | +- entering group authenticate {...} 177 | pam_pass: using pamauth string for pam.conf lookup 178 | pam_pass: authentication succeeded for 179 | ++[pam] returns ok 180 | +- entering group post-auth {...} 181 | ++[exec] returns noop 182 | Sending Access-Accept of id 69 to 127.0.0.1 port 38575 183 | Finished request 0. 184 | Going to the next request 185 | Waking up in 4.9 seconds. 186 | Cleaning up request 0 ID 69 with timestamp +17 187 | Ready to process requests. 188 | ------ 189 | 190 | Testing a OTP replay : 191 | ---------------------- 192 | 193 | Run the command again, with the _same_ OTP : 194 | 195 | ------ 196 | radtest alice ccccccccltncdjjifceergtnukivgiujhgehgnkrfcef 127.0.0.1 0 testing123 197 | ------ 198 | 199 | Then output should be like this, since the OTP was replayed: 200 | 201 | ------ 202 | Sending Access-Request of id 32 to 127.0.0.1 port 1812 203 | User-Name = "alice" 204 | User-Password = "ccccccccltncdjjifceergtnukivgiujhgehgnkrfcef" 205 | NAS-IP-Address = 127.0.1.1 206 | NAS-Port = 0 207 | rad_recv: Access-Reject packet from host 127.0.0.1 port 1812, id=32, length=20 208 | ------ 209 | 210 | PAM debug log: 211 | 212 | ------ 213 | [pam_yubico.c:parse_cfg(404)] called. 214 | [pam_yubico.c:parse_cfg(405)] flags 0 argc 3 215 | [pam_yubico.c:parse_cfg(407)] argv[0]=id=1 216 | [pam_yubico.c:parse_cfg(407)] argv[1]=debug 217 | [pam_yubico.c:parse_cfg(407)] argv[2]=authfile=/etc/yubikey_mapping 218 | [pam_yubico.c:parse_cfg(408)] id=1 219 | [pam_yubico.c:parse_cfg(409)] key=(null) 220 | [pam_yubico.c:parse_cfg(410)] debug=1 221 | [pam_yubico.c:parse_cfg(411)] alwaysok=0 222 | [pam_yubico.c:parse_cfg(412)] verbose_otp=0 223 | [pam_yubico.c:parse_cfg(413)] try_first_pass=0 224 | [pam_yubico.c:parse_cfg(414)] use_first_pass=0 225 | [pam_yubico.c:parse_cfg(415)] authfile=/etc/yubikey_mapping 226 | [pam_yubico.c:parse_cfg(416)] ldapserver=(null) 227 | [pam_yubico.c:parse_cfg(417)] ldap_uri=(null) 228 | [pam_yubico.c:parse_cfg(418)] ldapdn=(null) 229 | [pam_yubico.c:parse_cfg(419)] user_attr=(null) 230 | [pam_yubico.c:parse_cfg(420)] yubi_attr=(null) 231 | [pam_yubico.c:pam_sm_authenticate(452)] get user returned: alice 232 | [pam_yubico.c:pam_sm_authenticate(542)] conv returned: ccccccccltncdjjifceergtnukivgiujhgehgnkrfcef 233 | [pam_yubico.c:pam_sm_authenticate(558)] OTP: ccccccccltncdjjifceergtnukivgiujhgehgnkrfcef ID: ccccccccltnc 234 | [pam_yubico.c:pam_sm_authenticate(583)] ykclient return value (2): YubiKey OTP was replayed (REPLAYED_OTP) 235 | [pam_yubico.c:pam_sm_authenticate(625)] done. [Authentication failure] 236 | ------ 237 | 238 | FreeRadius debug log: 239 | 240 | ------ 241 | rad_recv: Access-Request packet from host 127.0.0.1 port 55170, id=32, length=89 242 | User-Name = "alice" 243 | User-Password = "ccccccccltncdjjifceergtnukivgiujhgehgnkrfcef" 244 | NAS-IP-Address = 127.0.1.1 245 | NAS-Port = 0 246 | +- entering group authorize {...} 247 | ++[preprocess] returns ok 248 | ++[chap] returns noop 249 | ++[mschap] returns noop 250 | [suffix] No '@' in User-Name = "alice", looking up realm NULL 251 | [suffix] No such realm "NULL" 252 | ++[suffix] returns noop 253 | [eap] No EAP-Message, not doing EAP 254 | ++[eap] returns noop 255 | [files] users: Matched entry DEFAULT at line 204 256 | ++[files] returns ok 257 | ++[expiration] returns noop 258 | ++[logintime] returns noop 259 | Found Auth-Type = PAM 260 | +- entering group authenticate {...} 261 | pam_pass: using pamauth string for pam.conf lookup 262 | pam_pass: function pam_authenticate FAILED for . Reason: Permission denied 263 | ++[pam] returns reject 264 | Failed to authenticate the user. 265 | Using Post-Auth-Type Reject 266 | +- entering group REJECT {...} 267 | [attr_filter.access_reject] expand: %{User-Name} -> alice 268 | attr_filter: Matched entry DEFAULT at line 11 269 | ++[attr_filter.access_reject] returns updated 270 | Delaying reject of request 1 for 1 seconds 271 | Going to the next request 272 | Waking up in 0.5 seconds. 273 | Sending delayed reject for request 1 274 | Sending Access-Reject of id 32 to 127.0.0.1 port 55170 275 | Waking up in 4.9 seconds. 276 | Cleaning up request 1 ID 32 with timestamp +66 277 | Ready to process requests. 278 | ------ 279 | -------------------------------------------------------------------------------- /doc/YubiKey_and_FreeRADIUS_1FA_via_PAM.adoc: -------------------------------------------------------------------------------- 1 | Yubico PAM Single-factor configuration guide 2 | -------------------------------------------- 3 | 4 | Step by Step Guide for Configuration of Yubico PAM module to provide single 5 | factor YubiKey OTP authentication for RADIUS server. 6 | 7 | Introduction 8 | ------------ 9 | The purpose of this document is to guide readers through the configuration 10 | steps to enable single factor authentication using YubiKey and RADIUS server 11 | on Linux platform. This document assumes that the reader has advance knowledge 12 | and experience in Linux system administration, particularly how to configure 13 | PAM authentication mechanism on a Linux platform. 14 | 15 | 16 | Details 17 | ------- 18 | 19 | Prerequisites 20 | ------------- 21 | 22 | Successful configuration of the Yubico PAM module to support single factor 23 | authentication for RADIUS requires following prerequisites: 24 | 25 | * Operating System: Any Unix operating system which supports PAM 26 | (Pluggable Authentication Module) 27 | (http://www.kernel.org/pub/linux/libs/pam/) 28 | * Complier : GNU GCC complier (http://gcc.gnu.org/) 29 | * FreeRADIUS: FreeRADIUS Version: 1.1.7 or *later* 30 | (http://freeradius.org/download.html) 31 | * Yubico PAM Module: Yubico PAM Module Version 1.8 32 | (https://developers.yubico.com/yubico-pam/) 33 | 34 | 35 | Configuration 36 | ------------- 37 | 38 | Configuration of FreeRADIUS server to support PAM authentication : 39 | ------------------------------------------------------------------ 40 | 41 | * Edit the radiusd configuration file “/etc/raddb/radiusd.conf” to make 42 | following changes: 43 | ------ 44 | * Change user and group to “root” to provide the root privileges to 45 | radiusd daemon so that it can call and use pam modules for authentication. 46 | NOTE: Generally, it is not a good security practice to assign root 47 | privileges to a user for a daemon. However, since use of PAM requires root 48 | privileges, this is a mandatory step here. 49 | * In “authenticate” section uncomment pam to direct radiusd daemon to use PAM 50 | module for authentication 51 | ------ 52 | * Edit the client configuration file “/etc/raddb/clients.conf” 53 | ------ 54 | Add sample client for testing 55 | ------ 56 | * Edit the user configuration file “/etc/raddb/users” to make following change: 57 | ------ 58 | Change "DEFAULT Auth-Type = System" to "DEFAULT Auth-Type = pam" for using 59 | PAM modules for user authentication 60 | ------ 61 | 62 | Installation of pam_yubico module : 63 | ----------------------------------- 64 | Build instructions for pam_yubico are available in the README. 65 | (https://developers.yubico.com/yubico-pam/) 66 | 67 | Configuration of pam_yubico module : 68 | ------------------------------------ 69 | Configuration instructions for pam_yubico are also available in the README. 70 | (https://developers.yubico.com/yubico-pam/) 71 | 72 | _Make sure you set your system up for either central authorization mapping, 73 | or user level mapping, as this will control which users can connect to the 74 | system using RADIUS._ 75 | 76 | Configuration of modified pam_yubico.so module at administrative level : 77 | ------------------------------------------------------------------------ 78 | 79 | Append the following line to the beginning of /etc/pam.d/radiusd file: 80 | 81 | ------ 82 | auth required pam_yubico.so id=16 debug authfile=/etc/yubikey_mappings 83 | ------ 84 | 85 | After the above configuration changes, whenever a user connects to the 86 | server using any RADIUS client, the PAM authentication interface will pass 87 | the control to Yubico PAM module. 88 | 89 | The Yubico PAM module first checks the presence of authfile argument in PAM 90 | configuration. If authfile argument is present, it parses the corresponding 91 | mapping file and verifies the username with corresponding YubiKey PublicID 92 | as configured in the mapping file. 93 | 94 | If valid, the Yubico PAM module extracts the OTP string and sends it to the 95 | Yubico authentication server or else it reports failure. If authfile argument 96 | is present but the mapping file is not present at the provided path PAM 97 | module reports failure. After successful verification of OTP Yubico PAM module 98 | from the Yubico authentication server, a success code is returned. 99 | 100 | 101 | User Level : 102 | ------------ 103 | 104 | Although, user level configuration of pam_yubico is possible, this might not 105 | be a desired configuration option in case of radisud daemon in most enterprise. 106 | 107 | 108 | Configuration of selinux policy to create exception for radiusd daemon : 109 | ----------------------------------------------------------------------- 110 | Local effective selinux policy must be updated to provide sufficient 111 | privileges to radiusd daemon on system resources. Please follow the steps below 112 | to configure effective selinux policy for radiusd daemon: 113 | 114 | * Start the radiusd daemon 115 | * Test the RADIUS authentication with the test case provided in “Testing the 116 | configuration” section below 117 | * As radiusd daemon doesn’t have sufficient selinux privileges to access the 118 | system resources required for using pam modules, the RADIUS authentication 119 | will fail. 120 | * This will create the logs in either “/var/log/messages” or in 121 | “/var/log/audit/audit.log” depending on the selinux configuration. 122 | * We can use audit2allow utility to provide selinux privileges to radiusd by 123 | using following sequence of commands: 124 | 125 | ------ 126 | [root@testsrv ~]# audit2allow -m local -l -i /var/log/messages > local.te 127 | 128 | [root@testsrv ~]# checkmodule -M -m -o local.mod local.te 129 | 130 | [root@testsrv ~]# semodule_package -o local.pp -m local.mod 131 | 132 | [root@testsrv ~]# semodule -i local.pp 133 | ------ 134 | 135 | For more selinux policy updating information and explanation of above commands 136 | please visit the following website: 137 | 138 | http://fedora.redhat.com/docs/selinux-faq-fc5/#id2961385 139 | 140 | 141 | Configuration of FreeRADIUS PAM file : 142 | -------------------------------------- 143 | 144 | FreeRADIUS server first authorizes presence of user in the configured database 145 | and then authenticates it. 146 | 147 | In a single factor YubiKey authentication, we must provide a PAM mechanism to 148 | authorize the presence of user. 149 | 150 | We are using “pam_listfile.so” PAM module to provide a PAM mechanism to 151 | authorize the presence of user. For this, we need to provide a file containing 152 | authorized user names to the “pam_listfile.so” file. This file should contain 153 | user names with only one user name on each line as follows: 154 | 155 | For example: 156 | 157 | ------ 158 | paul 159 | ------ 160 | 161 | The user_name file must be created/updated manually before configuration of 162 | Yubico PAM module for RADIUS authentication. Once this file is ready, we need 163 | to edit the FreeRADIUS PAM configuration file “/etc/pam.d/radiusd” and replace 164 | its contents with following lines: 165 | 166 | ------ 167 | auth sufficient pam_yubico.so id=16 debug authfile=/etc/yubikeyid 168 | account required pam_listfile.so onerr=fail item=user sense=allow file= 169 | ------ 170 | 171 | 172 | Test Setup : 173 | ------------ 174 | 175 | Our test environment is as follows: 176 | 177 | * Operating System: Fedora release 8 (Werewolf) 178 | * FreeRADIUS Server : FreeRADIUS Version 1.1.7 and Version 2.1.3 179 | * Yubico PAM: pam_yubico Version 1.8 180 | * "/etc/pam.d/radiusd" file: 181 | 182 | ------ 183 | auth sufficient pam_yubico.so id=16 debug authfile=/etc/yubikeyid 184 | account required pam_listfile.so onerr=fail item=user sense=allow file=/etc/yubicousers 185 | ------ 186 | 187 | Testing the configuration : 188 | --------------------------- 189 | 190 | We have tested the pam_yubico configuration on following Linux sever platforms: 191 | 192 | * Fedora 8: 193 | ------ 194 | * Operating system: Fedora release 8 (Werewolf) 195 | * FreeRADIUS Server : FreeRADIUS Version 1.1.7 and Version 2.1.3 196 | * Yubico PAM: pam_yubico Version 1.8 197 | ------ 198 | * Fedora 6: 199 | ------ 200 | * Operating system: Fedora Core release 6 (Zod) 201 | * FreeRADIUS Server : FreeRADIUS Version 1.1.7 and Version 2.1.3 202 | * Yubico PAM: pam_yubico Version 1.8 203 | ------ 204 | 205 | To test the RADIUS single factor authentication with YubiKey, we can use 206 | “radtest” radius client. The command is as follows: 207 | 208 | ------ 209 | [root@testsrv ~]# radtest {username} \ 210 | {password followed by YubiKey generated OTP} \ 211 | {radius-server}:{radius server port} \ 212 | {nas-port-number} \ 213 | {secret/ppphint/nasname} 214 | 215 | [root@testsrv ~]# radtest test vrkvfefuitvfvgu...ildbdk 127.0.0.1 0 testing123 216 | ------ 217 | 218 | 219 | Note : 220 | ------ 221 | The FreeRADIUS server version 1.1.3 seems to have problems regarding memory 222 | management and it may result in Segmentation Fault if configured with Yubico 223 | PAM module. We recommend using FreeRADIUS server version 1.1.7 or above. 224 | -------------------------------------------------------------------------------- /doc/YubiKey_and_FreeRADIUS_via_PAM.adoc: -------------------------------------------------------------------------------- 1 | == Yubico PAM Two-factor configuration guide == 2 | 3 | Step by Step Guide for Configuration of Yubico PAM module to provide Two-factor 4 | legacy Username + password + YubiKey OTP authentication for RADIUS server. 5 | 6 | === Introduction === 7 | The purpose of this document is to guide readers through the configuration 8 | steps to enable two factor authentication using YubiKey and RADIUS server on 9 | Linux platform. This document assumes that the reader has advance knowledge 10 | and experience in Linux system administration, particularly how to configure 11 | PAM authentication mechanism on a Linux platform. 12 | 13 | Although this configuration guide focuses on configuration of radiusd daemon for 14 | local authentication using the custom database (we have used /etc/passwd), 15 | radiusd can be configured easily to use centralized LDAP database for 16 | authentication or any popular directory service by configuring appropriate PAM 17 | modules in radiusd PAM configuration file. 18 | 19 | 20 | === Prerequisites === 21 | Successful configuration of the Yubico PAM module to support two factor 22 | authentication for RADIUS requires following prerequisites: 23 | 24 | Operating System:: 25 | Any Unix operating system which supports http://www.kernel.org/pub/linux/libs/pam[PAM] 26 | (Pluggable Authentication Module) 27 | 28 | Complier:: http://gcc.gnu.org[GNU GCC complier] 29 | 30 | http://freeradius.org/download.html[FreeRADIUS]:: Version: 1.1.7 or later 31 | 32 | https://developers.yubico.com/yubico-pam[Yubico PAM Module]:: Version 1.8 33 | 34 | === Configuration === 35 | We assume that FreeRADIUS is already installed on the server. 36 | 37 | ==== Configuration of FreeRADIUS server to support PAM authentication ==== 38 | 39 | * Edit the radiusd configuration file `/etc/raddb/radiusd.conf` to make 40 | following changes: 41 | 42 | - Change user and group to “root” to provide the root privileges to 43 | radiusd daemon so that it can call and use pam modules for authentication. 44 | 45 | - In “authenticate” section uncomment pam to direct radiusd daemon to use PAM 46 | module for authentication 47 | 48 | NOTE: Generally, it is not a good security practice to assign root 49 | privileges to a user for a daemon. However, since use of PAM requires root 50 | privileges, this is a mandatory step here. 51 | 52 | * Add sample client for testing in the client configuration 53 | file `/etc/raddb/clients.conf`. 54 | 55 | * Edit the user configuration file `/etc/raddb/users`, changing 56 | `DEFAULT Auth-Type = System` to `DEFAULT Auth-Type = pam` for using 57 | PAM modules for user authentication. 58 | 59 | 60 | === Installation of pam_yubico module === 61 | 62 | Build instructions for pam_yubico are available in the README. 63 | (https://developers.yubico.com/yubico-pam/) 64 | 65 | 66 | === Configuration of pam_yubico module === 67 | 68 | Configuration instructions for pam_yubico are also available in the README. 69 | (https://developers.yubico.com/yubico-pam/) 70 | 71 | NOTE: Make sure you set your system up for either central authorization mapping, 72 | or user level mapping, as this will control which users can connect to the 73 | system using RADIUS. 74 | 75 | 76 | === Configuration of modified pam_yubico.so module at administrative level === 77 | 78 | Append the following line to the beginning of /etc/pam.d/radiusd file: 79 | 80 | auth required pam_yubico.so id=16 debug authfile=/etc/yubikey_mappings 81 | 82 | After the above configuration changes, whenever a user connects to the 83 | server using any RADIUS client, the PAM authentication interface will pass 84 | the control to Yubico PAM module. 85 | 86 | The Yubico PAM module first checks the presence of authfile argument in PAM 87 | configuration. If authfile argument is present, it parses the corresponding 88 | mapping file and verifies the username with corresponding YubiKey PublicID 89 | as configured in the mapping file. 90 | 91 | If valid, the Yubico PAM module extracts the OTP string and sends it to the 92 | Yubico authentication server or else it reports failure. If authfile argument 93 | is present but the mapping file is not present at the provided path PAM 94 | module reports failure. After successful verification of OTP Yubico PAM module 95 | from the Yubico authentication server, a success code is returned. 96 | 97 | 98 | ==== User Level ==== 99 | 100 | Although, user level configuration of pam_yubico is possible, this might not 101 | be a desired configuration option in case of radisud daemon in most enterprise. 102 | 103 | 104 | === Configuration of SElinux policy to create exception for radiusd daemon === 105 | Local effective SElinux policy must be updated to provide sufficient 106 | privileges to radiusd daemon on system resources. Please follow the steps below 107 | to configure effective selinux policy for radiusd daemon: 108 | 109 | * Start the radiusd daemon 110 | * Test the RADIUS authentication with the test case provided in “Testing the 111 | configuration” section below 112 | * As radiusd daemon doesn’t have sufficient selinux privileges to access the 113 | system resources required for using pam modules, the RADIUS authentication 114 | will fail. 115 | * This will create the logs in either “/var/log/messages” or in 116 | “/var/log/audit/audit.log” depending on the selinux configuration. 117 | * We can use audit2allow utility to provide selinux privileges to radiusd by 118 | using following sequence of commands: 119 | 120 | ---- 121 | [root@testsrv ~]# audit2allow -m local -l -i /var/log/messages > local.te 122 | 123 | [root@testsrv ~]# checkmodule -M -m -o local.mod local.te 124 | 125 | [root@testsrv ~]# semodule_package -o local.pp -m local.mod 126 | 127 | [root@testsrv ~]# semodule -i local.pp 128 | ---- 129 | 130 | For more selinux policy updating information and explanation of above commands 131 | please visit the following website: 132 | 133 | http://fedora.redhat.com/docs/selinux-faq-fc5/#id2961385 134 | 135 | 136 | === Test Setup === 137 | 138 | Our test environment is as follows: 139 | 140 | [horizontal] 141 | *Operating System*:: Fedora release 8 (Werewolf) 142 | *FreeRADIUS Server*:: Version 1.1.7 143 | *Yubico PAM*:: Version 1.8 144 | */etc/pam.d/radiusd file*:: 145 | + 146 | ---- 147 | auth required pam_yubico.so authfile=/etc/yubikeyid id=16 debug 148 | auth include system-auth 149 | account required pam_nologin.so 150 | account include system-auth 151 | password include system-auth 152 | session include system-auth 153 | ---- 154 | 155 | 156 | === Testing the configuration === 157 | 158 | We have tested the pam_yubico configuration on following Linux sever platforms: 159 | 160 | Fedora 8: 161 | 162 | * Operating system: Fedora release 8 (Werewolf) 163 | * FreeRADIUS Server : FreeRADIUS Version 1.1.7 164 | * Yubico PAM: pam_yubico Version 1.8 165 | 166 | Fedora 6: 167 | 168 | * Operating system: Fedora Core release 6 (Zod) 169 | * FreeRADIUS Server : FreeRADIUS Version 1.1.7 170 | * Yubico PAM: pam_yubico Version 1.8 171 | 172 | To test the RADIUS two factor authentication with YubiKey, we can use 173 | 'radtest' radius client. The command is as follows: 174 | 175 | ---- 176 | [root@testsrv ~]# radtest {username} \ 177 | {password followed by YubiKey generated OTP} \ 178 | {radius-server}:{radius server port} \ 179 | {nas-port-number} \ 180 | {secret/ppphint/nasname} 181 | 182 | [root@testsrv ~]# radtest test test123vrkvit...bekkjc 127.0.0.1 0 testing123 183 | ---- 184 | 185 | 186 | NOTE: 187 | The FreeRADIUS server version 1.1.3 seems to have problems regarding memory 188 | management and it may result in Segmentation Fault if configured with Yubico 189 | PAM module. We recommend using FreeRADIUS server version 1.1.7 or above. 190 | -------------------------------------------------------------------------------- /doc/YubiKey_and_OpenVPN_via_PAM.adoc: -------------------------------------------------------------------------------- 1 | == Introduction 2 | 3 | The purpose of this document is to guide readers through the configuration steps to use two factor authentication for OpenVPN using YubiKey. This document assumes that the reader has advanced knowledge and experience in Linux system administration, particularly for how PAM authentication mechanism is configured on a Linux platform. 4 | 5 | 6 | == Prerequisites 7 | 8 | Successful configuration of the Yubico PAM module to support two factor authentication for OpenVPN has the following prerequisites: 9 | 10 | Operating System:: Any Unix operating system which supports 11 | http://www.kernel.org/pub/linux/libs/pam[PAM] (Pluggable Authentication Module) 12 | Complier:: http://gcc.gnu.org[GNU GCC complier] 13 | Software:: 14 | https://developers.yubico.com/yubico-pam[Yubico PAM Module] + 15 | http://openvpn.net/index.php/downloads.html[OpenVPN] + 16 | http://freeradius.org/download.html[FreeRADIUS] + 17 | Pam_Radius 18 | 19 | == Configuration 20 | 21 | There are two ways OpenVPN can be configured to support two factor authentication with YubiKey. 22 | 23 | === OpenVPN Configuration without FreeRADIUS support: 24 | 25 | In this mode of configuration, OpenVPN server will be authenticating users 26 | by verifying username and user’s password against system password file 27 | `/etc/passwd` and verifying OTP (one time password generated from YubiKey) 28 | against Yubico's OTP validation server. 29 | 30 | We assume that OpenVPN server is already installed on the server. 31 | 32 | ==== Configuration of OpenVPN server to support PAM authentication: 33 | 34 | * Edit the OpenVPN server configuration file `/etc/openvpn/server.conf` 35 | to add the following three lines to enable PAM modules for username 36 | and password authentication: 37 | 38 | plugin ::: …. 83 | :::….. 84 | ------ 85 | e.g.: 86 | 87 | ------ 88 | paul:indvnvlcbdre:ldvglinuddek 89 | simon:uturrufnjder:hjturefjtehv 90 | kurt:ertbhunjimko 91 | ------ 92 | 93 | The mapping file must be created/updated manually before configuration 94 | of Yubico PAM module for OpenVPN authentication. 95 | 96 | 97 | ====== Configuration of modified pam_yubico.so module at administrative level: 98 | 99 | Append the following line to the beginning of /etc/pam.d/radiusd file: 100 | 101 | auth required pam_yubico.so id=16 debug authfile=/path/to/mapping/file 102 | 103 | After the above configuration changes, whenever a user connects to the 104 | server using any RADIUS client, the PAM authentication interface will 105 | pass the control to Yubico PAM module. 106 | 107 | The Yubico PAM module first checks the presence of authfile argument 108 | in PAM configuration. If authfile argument is present, it parses the 109 | corresponding mapping file and verifies the username with corresponding 110 | YubiKey PublicID as configured in the mapping file. If valid, the Yubico 111 | PAM module extracts the OTP string and sends it to the Yubico 112 | authentication server or else it reports failure. If authfile argument 113 | is present but the mapping file is not present at the provided path PAM 114 | module reports failure. 115 | 116 | After successful verification of OTP Yubico PAM module from the Yubico 117 | authentication server, a success code is returned. 118 | 119 | 120 | ===== User Level 121 | 122 | Although, user level configuration of pam_yubico is possible, this might 123 | not be a desired configuration option in case of OpenVPN daemon in most 124 | enterprise. 125 | 126 | ====== Configuration of PAM modules for OpenVPN: 127 | 128 | To configure PAM modules for OpenVPN, create a file named 129 | `/etc/pam.d/openvpn` (file name must be one which is specified 130 | in `/etc/openvpn/server.conf` along with 'plugin' directive) 131 | and list all the PAM modules in this files accordingly. 132 | 133 | ==== Test Setup 134 | 135 | Our test environment is as follows: 136 | 137 | Operating System:: Fedora release 8 (Werewolf) 138 | 139 | OpenVPN Server:: OpenVPN Version 2.0.9 140 | 141 | Yubico PAM:: pam_yubico Version 1.8 142 | 143 | /etc/pam.d/openvpn file:: 144 | ---- 145 | auth required pam_yubico.so authfile=/etc/yubikeyid id=16 debug 146 | auth include system-auth 147 | account required pam_nologin.so 148 | account include system-auth 149 | password include system-auth 150 | session include system-auth 151 | ---- 152 | 153 | ==== Testing the configuration 154 | 155 | We have tested the pam_yubico configuration on following Linux sever platforms: 156 | 157 | i) Fedora 8: 158 | 159 | Operating system: Fedora release 8 (Werewolf), 160 | OpenVPN Server : OpenVPN Version 2.0.9, 161 | Yubico PAM: pam_yubico Version 1.8 162 | 163 | ii) Fedora 6: 164 | 165 | Operating system: Fedora Core release 6 (Zod), 166 | OpenVPN Server: OpenVPN Version 2.0.9, 167 | Yubico PAM: pam_yubico version 1.8 168 | 169 | To test the configuration, first create a couple of test users on the 170 | system where OpenVPN server is running and configure their YubiKey IDs 171 | accordingly. 172 | 173 | Please use the following command for testing: 174 | 175 | ------ 176 | [root@testsrv ~]# openvpn /etc/openvpn/client.conf 177 | ------ 178 | 179 | OpenVPN client will first prompt for username, enter the username. 180 | After that OpenVPN client will prompt for password, enter user’s password 181 | immediately followed by an OTP generated by a YubiKey. 182 | 183 | If OpenVPN server is configured for supporting PAM authentication, it 184 | will verify user authentication details even at the startup of OpenVPN 185 | server demon, when it is started using `init.d` script or it is 186 | configured to start at boot time. 187 | 188 | To avoid prompting of username and password at the startup of OpenVPN 189 | server demon, we can start OpenVPN Server demon at command line as 190 | follows instead of starting it using `init.d` script: 191 | 192 | ------ 193 | [root@testsrv ~]# /usr/sbin/openvpn --config /etc/openvpn/server.conf --daemon openvpn 194 | ------ 195 | 196 | We can configure OpenVPN server demon to start at boot time by 197 | copying the above command in `/etc/rc.local` file. 198 | 199 | === OpenVPN Configuration with FreeRADIUS support 200 | 201 | In this type of configuration, the OpenVPN server will be using 202 | FreeRADIUS server for authenticating users. FreeRADIUS server will 203 | be verifying the authentication information received from OpenVPN 204 | server by verifying the username and user’s password against system 205 | password file `/etc/passwd` (or by other means supported by FreeRADIUS) 206 | and verifying the OTP (one time password) generated by a YubiKey 207 | with the Yubico’s OTP validation server. 208 | 209 | To configure OpenVPN with FreeRADIUS support, please follow the steps below: 210 | 211 | * Follow all the steps mentioned in the section “OpenVPN Configuration without FreeRADIUS support” to configure OpenVPN server to support PAM authentication. 212 | 213 | * https://developers.yubico.com/yubico-pam/YubiKey_and_FreeRADIUS_via_PAM.html[Install and configure FreeRADIUS server for two factor authentication]. 214 | 215 | * Install and configure pam_radius_auth.so and copy it to /lib/security directory 216 | 217 | * Create a file `/etc/pam.d/openvpn` (file name must be the one which is specified 218 | in `/etc/openvpn/server.conf` along with 'plugin' directive) and copy the following 219 | contents to the file: 220 | 221 | ------ 222 | account required pam_radius_auth.so 223 | account required pam_radius_auth.so 224 | auth required pam_radius_auth.so no_warn try_first_pass 225 | ------ 226 | 227 | * Create a file `/etc/raddb/server` to configure FreeRADIUS server that is 228 | used by `pam_radius_auth` PAM module. The content for the file is as follows: 229 | 230 | ------ 231 | 232 | 233 | 234 | . 235 | . 236 | . 237 | ------ 238 | 239 | e.g.: 240 | 241 | ------ 242 | freeradius.example.com Admin456 243 | ------ 244 | 245 | We can configure failover support for RADIUS server by creating additional 246 | RADIUS server entries per line of ´/etc/raddb/server´ file. 247 | 248 | ==== Test Setup 249 | 250 | Our test environment is as follows: 251 | 252 | * `/etc/pam.d/openvpn` file: 253 | 254 | ------ 255 | account required pam_radius_auth.so 256 | account required pam_radius_auth.so 257 | auth required pam_radius_auth.so no_warn try_first_pass 258 | ------ 259 | 260 | ==== Testing the configuration 261 | 262 | We have tested the pam_yubico configuration on following Linux sever platforms: 263 | 264 | * Fedora 8 265 | * Fedora 6 266 | 267 | To test the configuration, first create a couple of test users 268 | on the system where FreeRADIUS server is running and configure 269 | their YubiKey IDs accordingly. 270 | 271 | Please use the following command for testing: 272 | 273 | ------ 274 | [root@varsha ~]# openvpn /etc/openvpn/client.conf 275 | ------ 276 | 277 | OpenVPN client will first prompt for username, enter the username. 278 | After that OpenVPN client will prompt for password, enter user’s 279 | password immediately followed by an OTP generated by a YubiKey. 280 | -------------------------------------------------------------------------------- /doc/YubiKey_and_Radius_via_PAM.adoc: -------------------------------------------------------------------------------- 1 | Introduction 2 | ------------ 3 | 4 | The purpose of this page is to collect all information needed to set up a Radius server that can use the pam_yubico module to provide user authentication via Radius. 5 | 6 | Details 7 | ------- 8 | 9 | We currently use FreeRadius. The paths below may be specific to Debian's packages, please update this if you have paths for other systems. 10 | 11 | Build pam_yubico and install FreeRadius 12 | --------------------------------------- 13 | 14 | Build instructions for pam_yubico are found in the pam_yubico ReadMe. 15 | 16 | Install FreeRadius from your OS vendor packages: 17 | 18 | Debian/Ubuntu: 19 | 20 | $ sudo apt-get install freeradius 21 | 22 | 23 | == Add a Radius client stanza to /etc/freeradius/clients.conf 24 | 25 | For testing, add something like: 26 | 27 | ------ 28 | client 0.0.0.0/0 { 29 | secret = pencil 30 | shortname = radius.yubico.com 31 | } 32 | ------ 33 | 34 | Configure FreeRadius so that it uses PAM 35 | ---------------------------------------- 36 | 37 | In /etc/freeradius/radiusd.conf, check that 'pam' is uncommented in the 'authenticate' section. 38 | 39 | Configure PAM for the Radius server 40 | ----------------------------------- 41 | 42 | The PAM service is 'radiusd', and the configuration file is stored in /etc/pam.d/radiusd. Add something like: 43 | 44 | auth sufficient pam_yubico.so id=16 debug 45 | 46 | 47 | Start FreeRadius in debug mode and test it 48 | ------------------------------------------ 49 | 50 | As root, run: 51 | 52 | # /usr/sbin/freeradiusd -X 53 | 54 | Then invoke a test client as follows: 55 | 56 | $ radtest yubico vlrlcingbbkrctguicnijbegfjhrdhccefdthcuifkgr 127.0.0.1 0 pencil 57 | 58 | If you get errors about non-existing user, you may need to create a Unix user 'yubico'. Whether this should be needed or not depends on PAM configuration. 59 | -------------------------------------------------------------------------------- /doc/YubiKey_and_SELinux.adoc: -------------------------------------------------------------------------------- 1 | == Enable HTTP connection for sshd 2 | 3 | Starting with Fedora 17, SELinux prevents sshd to initiate connections to remote HTTP ports (80 and 443). In SELinux terms: sshd_t is not allowed to name_connect to http_port_t. This broke YubiKey authentication on a system with SELinux in enforcing mode, unless a custom SELinux policy was written and enabled. 4 | 5 | Based on a https://bugzilla.redhat.com/show_bug.cgi?id=841693[bugreport] in Red Hat Bugzilla, a boolean was added to the SELinux policy for Fedora 18 and up, that can be toggled to allow sshd (and some other SELinux types) to connect to remote HTTP ports. 6 | 7 | To make a long story short, if you want to use a YubiKey on a system running Fedora 18 or higher (and probably RHEL7, eventually), you'll need to toggle the 'authlogin_yubikey' SELinux boolean, like so: 8 | 9 | setsebool -P authlogin_yubikey 1 10 | 11 | If you are using your own server via `urllist`/`url` in the pam conf file and using a non-standard http port, you will need to add that port to the `http_port_t` port list. For example, port `12345`: 12 | 13 | semanage port -a -t http_port_t -p tcp 12345 14 | 15 | == Enable debug_file support for sshd 16 | 17 | By default, SELinux prevents sshd from opening local files other than SSH configuration files. If you would like to debug this module using `debug` and `debug_file` parameters, you may need to temporarily relax your SELinux confinement: 18 | 19 | setenforce permissive 20 | 21 | Don't forget to re-enable SELinux once you're done: 22 | 23 | setenforce enforcing 24 | -------------------------------------------------------------------------------- /doc/YubiKey_and_SSH_via_PAM.adoc: -------------------------------------------------------------------------------- 1 | == Introduction == 2 | 3 | The purpose of this document is to guide readers through the configuration 4 | steps to use two factor authentication for SSH using YubiKey. This document 5 | assumes that the reader has advanced knowledge and experience in Linux 6 | system administration, particularly for how PAM authentication mechanism is 7 | configured on a Linux platform. 8 | 9 | == Prerequisites == 10 | 11 | Successful configuration of the Yubico PAM module to support two factor 12 | authentication requires following prerequisites: 13 | 14 | Operating System:: 15 | Any Unix operating system which supports PAM 16 | (http://www.kernel.org/pub/linux/libs/pam[Pluggable Authentication Module]) 17 | 18 | Complier:: http://gcc.gnu.org[GNU GCC complier] 19 | 20 | https://developers.yubico.com/yubico-c-client[Yubico Client C library]:: 21 | Version 1.5 or later 22 | 23 | https://developers.yubico.com/yubico-pam[Yubico PAM Module]:: Version 1.7 or later 24 | 25 | == System Requirements == 26 | 27 | This document illustrates the configuration steps for Fedora Core 8 28 | operating system. However, there steps should work on most other Linux 29 | distributions. 30 | 31 | The Yubico PAM module for SSH can be downloaded from 32 | https://developers.yubico.com/yubico-pam/releases.html[here]. 33 | 34 | The Yubico PAM module support two factor authentication for SSH. 35 | The two factor authentication module verifies the user name and password 36 | for the user and the One-Time Password (OTP) generated by YubiKey assigned 37 | to the user. 38 | 39 | 40 | == Build yubico-c-client and pam_yubico == 41 | 42 | Build instructions for yubico-c-client and pam_yubico are found in their 43 | respective README. 44 | 45 | 46 | == Configuration == 47 | 48 | === Configuration for user and YubiKey token ID mapping === 49 | 50 | There are two ways of user and YubiKey token ID mapping. It can be either 51 | done at administrative level or at individual user level. 52 | 53 | ==== Administrative Level ==== 54 | 55 | In Administrative level, system administrators hold right to configure the 56 | user and YubiKey token ID mapping. Administrators can achieve this by creating 57 | a new file that contains information about the username and the corresponding 58 | IDs of YubiKey(s) assigned. 59 | 60 | This file contains user name that is allowed to connect to the system over SSH 61 | and the token id of the YubiKey(s) assigned to that particular user. A user 62 | can be assigned multiple YubiKeys and this multi key mapping is supported by 63 | this file. However, presently there is no logic coded to detect or prevent use 64 | of same YubiKey ID for multiple users. 65 | 66 | Each record in the file should begin on a new line. The parameters in each 67 | record are separated by `:` character similar to `/etc/passwd`. 68 | 69 | The contents of this file are as follows: 70 | 71 | ::: …. 72 | 73 | :::….. 74 | 75 | e.g. 76 | 77 | -------- 78 | paul:indvnvlcbdre:ldvglinuddek 79 | simon:uturrufnjder:hjturefjtehv 80 | kurt:ertbhunjimko 81 | -------- 82 | 83 | The mapping file must be created/updated manually before configuration of 84 | Yubico PAM module for SSH authentication. 85 | 86 | ===== Configuration of modified pam_yubico.so module at administrative level ===== 87 | 88 | Append the following line to the beginning of the `/etc/pam.d/sshd` file: 89 | 90 | auth required pam_yubico.so id=16 debug authfile=/path/to/mapping/file 91 | 92 | Make sure you set `id=16` to the correct API-id for the yubico validation server. 93 | 94 | After the above configuration changes, whenever a user connects to the server 95 | using any ssh client, the PAM authentication interface will pass the control to 96 | Yubico PAM module. The Yubico PAM module first checks the presence of authfile 97 | argument in PAM configuration. If authfile argument is present, it parses the 98 | corresponding mapping file and verifies the username with corresponding 99 | YubiKey token id as configured in the mapping file. If valid, the Yubico PAM 100 | module extracts the OTP string and sends it to the Yubico authentication server 101 | or else it reports failure. If authfile argument is present but the mapping 102 | file is not present at the provided path PAM module reports failure. After 103 | successful verification of OTP Yubico PAM module from the Yubico 104 | authentication server, a success code is returned. 105 | 106 | 107 | ==== User Level ==== 108 | 109 | In User level, individual users have the ability to configure YubiKey token 110 | ID assigned to them. Users can achieve this by creating a new file 111 | `.yubico/authorized_yubikeys` inside their home directories that contains 112 | information about the username and the corresponding IDs of YubiKey(s) assigned 113 | to them. A user can be assigned multiple YubiKeys and the multi key mapping is 114 | supported by this file. 115 | 116 | This file must contain only one record. The parameters in the record are 117 | separated by `:` character similar to `/etc/passwd`. The contents of this file 118 | are as shown below: 119 | 120 | ::: …. 121 | 122 | e.g. 123 | 124 | paul:indvnvlcbdre:ldvglinuddek 125 | 126 | 127 | The `.yubico/authorized_yubikeys` file must be created/updated manually and must 128 | be placed inside user's home directory before configuration of Yubico PAM 129 | module for SSH authentication. 130 | 131 | 132 | ===== Configuration of modified pam_yubico.so module at user level ===== 133 | 134 | Append the following line to the beginning of the `/etc/pam.d/sshd` file: 135 | 136 | auth required pam_yubico.so id=16 debug 137 | 138 | After the above configuration changes, whenever a user connects to the server 139 | using any SSH client, the PAM authentication interface will pass the control 140 | to Yubico PAM module. The Yubico PAM module first verifies the username with 141 | corresponding YubiKey token id as configured in the `.yubico/authorized_yubikeys` 142 | file that present in the user's home directory who is trying to assess server 143 | through SSH. If valid, the Yubico PAM module extracts the OTP string and sends 144 | it to the Yubico authentication server or else it reports failure. After 145 | successful verification of OTP Yubico PAM module from the Yubico authentication 146 | server, a success code is returned. 147 | 148 | 149 | === pam_unix.so configuration === 150 | 151 | Append _try_first_pass_ parameter to the _pam_unix.so_ module to authenticate 152 | the user with password passed from the preceding auth module. 153 | 154 | The _pam_unix.so_ module used for authentication is generally located into 155 | `/etc/pam.d/system-auth` for RedHat based Linux system and into 156 | `/etc/pam.d/common-auth` for Debian based Linux systems. 157 | 158 | === SSH configuration === 159 | 160 | Edit the sshd configuration file `/etc/ssh/sshd_config`_ to disable challenge- 161 | response passwords. Change `challenge-response passwords yes` to 162 | `challenge-response passwords no`. 163 | 164 | 165 | == Test Setup == 166 | 167 | === Fedora 8 === 168 | 169 | Test setup for fedora 8 environment is as follows: 170 | 171 | OS Version:: Fedora release 8 (Werewolf) 172 | Kernel Version:: Kernel version 2.6.23.1-42.fc8 173 | OpenSSH Version:: openssh-4.7p1-2.fc8 174 | Yubico PAM Version:: pam_yubico-1.7 175 | 176 | === Fedora 6 === 177 | 178 | Test setup for fedora 6 environment is as follows: 179 | 180 | OS Version:: Fedora Core release 6 (Zod) 181 | Kernel Version:: Kernel version 2.6.18-1.2798.fc6 182 | OpenSSH Version:: openssh-4.3p2-10 183 | Yubico PAM Version:: pam_yubico-1.7 184 | 185 | 186 | === PAM configuration === 187 | 188 | PAM configuration files in our testing environment are as follows: 189 | 190 | 191 | ==== /etc/pam.d/sshd ==== 192 | ------- 193 | auth required pam_yubico.so authfile=/etc/yubikeyid id=16 debug 194 | auth include system-auth 195 | account required pam_nologin.so 196 | account include system-auth 197 | password include system-auth 198 | session optional pam_keyinit.so force revoke 199 | session include system-auth 200 | session required pam_loginuid.so 201 | ------- 202 | 203 | 204 | ==== /etc/yubikeyid ==== 205 | 206 | ------- 207 | root:indvnvlcbdre:ldvglinuddek 208 | test:ldvglinuddek 209 | ------- 210 | 211 | ===== /root/.yubico/authorized_yubikeys ===== 212 | 213 | ------- 214 | root:indvnvlcbdre:ldvglinuddek 215 | ------- 216 | 217 | Please change PAM configuration settings for SSH as shown above and test the 218 | configuration. 219 | 220 | 221 | == Testing the Configuration == 222 | 223 | We assume that you have 'root' and 'test' user configured to access SSH on your 224 | test environment with password 'secret' and 'pencil' respectively. 225 | 226 | Use any standard SSH client for testing (We used SSH command line utility). 227 | 228 | Try to login to server with SSH client as configured user: 229 | 230 | ------ 231 | $ ssh -l test localhost 232 | Password: (enter 'pencil' and touch the ldvglinuddek YubiKey) 233 | ------ 234 | 235 | ------ 236 | $ ssh -l root localhost 237 | Password: (enter 'secret' and touch the ldvglinuddek YubiKey) 238 | ------ 239 | 240 | ------ 241 | $ ssh -l root localhost 242 | Password: (enter 'secret' and touch the indvnvlcbdre YubiKey) 243 | ------ 244 | -------------------------------------------------------------------------------- /drop_privs.c: -------------------------------------------------------------------------------- 1 | /* Written by Ricky Zhou 2 | * Fredrik Thulin implemented pam_modutil_drop_priv 3 | * 4 | * Copyright (c) 2011-2014 Yubico AB 5 | * Copyright (c) 2011 Ricky Zhou 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are 10 | * met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * 15 | * * Redistributions in binary form must reproduce the above 16 | * copyright notice, this list of conditions and the following 17 | * disclaimer in the documentation and/or other materials provided 18 | * with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef HAVE_PAM_MODUTIL_DROP_PRIV 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include "drop_privs.h" 43 | #include "util.h" 44 | 45 | #ifdef HAVE_SECURITY_PAM_APPL_H 46 | #include 47 | #endif 48 | #ifdef HAVE_SECURITY_PAM_MODULES_H 49 | #include 50 | #endif 51 | 52 | 53 | int pam_modutil_drop_priv(pam_handle_t *pamh, struct _ykpam_privs *privs, struct passwd *pw) { 54 | privs->saved_euid = geteuid(); 55 | privs->saved_egid = getegid(); 56 | 57 | if ((privs->saved_euid == pw->pw_uid) && (privs->saved_egid == pw->pw_gid)) { 58 | D (privs->debug_file, "Privileges already dropped, pretend it is all right"); 59 | return 0; 60 | } 61 | 62 | privs->saved_groups_length = getgroups(0, NULL); 63 | if (privs->saved_groups_length < 0) { 64 | D (privs->debug_file, "getgroups: %s", strerror(errno)); 65 | return -1; 66 | } 67 | 68 | if (privs->saved_groups_length > SAVED_GROUPS_MAX_LEN) { 69 | D (privs->debug_file, "too many groups, limiting."); 70 | privs->saved_groups_length = SAVED_GROUPS_MAX_LEN; 71 | } 72 | 73 | if (privs->saved_groups_length > 0) { 74 | if (getgroups(privs->saved_groups_length, privs->saved_groups) < 0) { 75 | D (privs->debug_file, "getgroups: %s", strerror(errno)); 76 | goto free_out; 77 | } 78 | } 79 | 80 | if (initgroups(pw->pw_name, pw->pw_gid) < 0) { 81 | D (privs->debug_file, "initgroups: %s", strerror(errno)); 82 | goto free_out; 83 | } 84 | 85 | if (setegid(pw->pw_gid) < 0) { 86 | D (privs->debug_file, "setegid: %s", strerror(errno)); 87 | goto free_out; 88 | } 89 | 90 | if (seteuid(pw->pw_uid) < 0) { 91 | D (privs->debug_file, "seteuid: %s", strerror(errno)); 92 | goto free_out; 93 | } 94 | 95 | return 0; 96 | free_out: 97 | return -1; 98 | } 99 | 100 | int pam_modutil_regain_priv(pam_handle_t *pamh, struct _ykpam_privs *privs) { 101 | if ((privs->saved_euid == geteuid()) && (privs->saved_egid == getegid())) { 102 | D (privs->debug_file, "Privilges already as requested, pretend it is all right"); 103 | return 0; 104 | } 105 | 106 | if (seteuid(privs->saved_euid) < 0) { 107 | D (privs->debug_file, "seteuid: %s", strerror(errno)); 108 | return -1; 109 | } 110 | 111 | if (setegid(privs->saved_egid) < 0) { 112 | D (privs->debug_file, "setegid: %s", strerror(errno)); 113 | return -1; 114 | } 115 | 116 | if (setgroups(privs->saved_groups_length, privs->saved_groups) < 0) { 117 | D (privs->debug_file, "setgroups: %s", strerror(errno)); 118 | return -1; 119 | } 120 | 121 | return 0; 122 | } 123 | 124 | #endif // HAVE_PAM_MODUTIL_DROP_PRIV 125 | -------------------------------------------------------------------------------- /drop_privs.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2011-2014 Yubico AB 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following 13 | * disclaimer in the documentation and/or other materials provided 14 | * with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #ifndef __PAM_YUBICO_DROP_PRIVS_H_INCLUDED__ 30 | #define __PAM_YUBICO_DROP_PRIVS_H_INCLUDED__ 31 | 32 | #ifdef HAVE_PAM_MODUTIL_DROP_PRIV 33 | #include 34 | #else 35 | 36 | #include 37 | #include 38 | 39 | #ifdef HAVE_SECURITY_PAM_APPL_H 40 | #include 41 | #endif 42 | #ifdef HAVE_SECURITY_PAM_MODULES_H 43 | #include 44 | #endif 45 | 46 | #define SAVED_GROUPS_MAX_LEN 64 /* as pam_modutil.. */ 47 | 48 | struct _ykpam_privs { 49 | uid_t saved_euid; 50 | gid_t saved_egid; 51 | gid_t *saved_groups; 52 | int saved_groups_length; 53 | FILE *debug_file; 54 | }; 55 | 56 | #define PAM_MODUTIL_DEF_PRIVS(n) \ 57 | gid_t n##_saved_groups[SAVED_GROUPS_MAX_LEN]; \ 58 | struct _ykpam_privs n = {-1, -1, n##_saved_groups, SAVED_GROUPS_MAX_LEN, cfg->debug_file} 59 | 60 | int pam_modutil_drop_priv(pam_handle_t *, struct _ykpam_privs *, struct passwd *); 61 | int pam_modutil_regain_priv(pam_handle_t *, struct _ykpam_privs *); 62 | 63 | #endif 64 | #endif 65 | -------------------------------------------------------------------------------- /m4/lib-ld.m4: -------------------------------------------------------------------------------- 1 | # lib-ld.m4 serial 4 (gettext-0.18) 2 | dnl Copyright (C) 1996-2003, 2009 Free Software Foundation, Inc. 3 | dnl This file is free software; the Free Software Foundation 4 | dnl gives unlimited permission to copy and/or distribute it, 5 | dnl with or without modifications, as long as this notice is preserved. 6 | 7 | dnl Subroutines of libtool.m4, 8 | dnl with replacements s/AC_/AC_LIB/ and s/lt_cv/acl_cv/ to avoid collision 9 | dnl with libtool.m4. 10 | 11 | dnl From libtool-1.4. Sets the variable with_gnu_ld to yes or no. 12 | AC_DEFUN([AC_LIB_PROG_LD_GNU], 13 | [AC_CACHE_CHECK([if the linker ($LD) is GNU ld], [acl_cv_prog_gnu_ld], 14 | [# I'd rather use --version here, but apparently some GNU ld's only accept -v. 15 | case `$LD -v 2>&1 conf$$.sh 35 | echo "exit 0" >>conf$$.sh 36 | chmod +x conf$$.sh 37 | if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then 38 | PATH_SEPARATOR=';' 39 | else 40 | PATH_SEPARATOR=: 41 | fi 42 | rm -f conf$$.sh 43 | fi 44 | ac_prog=ld 45 | if test "$GCC" = yes; then 46 | # Check if gcc -print-prog-name=ld gives a path. 47 | AC_MSG_CHECKING([for ld used by GCC]) 48 | case $host in 49 | *-*-mingw*) 50 | # gcc leaves a trailing carriage return which upsets mingw 51 | ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; 52 | *) 53 | ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; 54 | esac 55 | case $ac_prog in 56 | # Accept absolute paths. 57 | [[\\/]* | [A-Za-z]:[\\/]*)] 58 | [re_direlt='/[^/][^/]*/\.\./'] 59 | # Canonicalize the path of ld 60 | ac_prog=`echo $ac_prog| sed 's%\\\\%/%g'` 61 | while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do 62 | ac_prog=`echo $ac_prog| sed "s%$re_direlt%/%"` 63 | done 64 | test -z "$LD" && LD="$ac_prog" 65 | ;; 66 | "") 67 | # If it fails, then pretend we aren't using GCC. 68 | ac_prog=ld 69 | ;; 70 | *) 71 | # If it is relative, then search for the first ld in PATH. 72 | with_gnu_ld=unknown 73 | ;; 74 | esac 75 | elif test "$with_gnu_ld" = yes; then 76 | AC_MSG_CHECKING([for GNU ld]) 77 | else 78 | AC_MSG_CHECKING([for non-GNU ld]) 79 | fi 80 | AC_CACHE_VAL([acl_cv_path_LD], 81 | [if test -z "$LD"; then 82 | IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}${PATH_SEPARATOR-:}" 83 | for ac_dir in $PATH; do 84 | test -z "$ac_dir" && ac_dir=. 85 | if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then 86 | acl_cv_path_LD="$ac_dir/$ac_prog" 87 | # Check to see if the program is GNU ld. I'd rather use --version, 88 | # but apparently some GNU ld's only accept -v. 89 | # Break only if it was the GNU/non-GNU ld that we prefer. 90 | case `"$acl_cv_path_LD" -v 2>&1 < /dev/null` in 91 | *GNU* | *'with BFD'*) 92 | test "$with_gnu_ld" != no && break ;; 93 | *) 94 | test "$with_gnu_ld" != yes && break ;; 95 | esac 96 | fi 97 | done 98 | IFS="$ac_save_ifs" 99 | else 100 | acl_cv_path_LD="$LD" # Let the user override the test with a path. 101 | fi]) 102 | LD="$acl_cv_path_LD" 103 | if test -n "$LD"; then 104 | AC_MSG_RESULT([$LD]) 105 | else 106 | AC_MSG_RESULT([no]) 107 | fi 108 | test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH]) 109 | AC_LIB_PROG_LD_GNU 110 | ]) 111 | -------------------------------------------------------------------------------- /m4/lib-prefix.m4: -------------------------------------------------------------------------------- 1 | # lib-prefix.m4 serial 6 (gettext-0.18) 2 | dnl Copyright (C) 2001-2005, 2008 Free Software Foundation, Inc. 3 | dnl This file is free software; the Free Software Foundation 4 | dnl gives unlimited permission to copy and/or distribute it, 5 | dnl with or without modifications, as long as this notice is preserved. 6 | 7 | dnl From Bruno Haible. 8 | 9 | dnl AC_LIB_ARG_WITH is synonymous to AC_ARG_WITH in autoconf-2.13, and 10 | dnl similar to AC_ARG_WITH in autoconf 2.52...2.57 except that is doesn't 11 | dnl require excessive bracketing. 12 | ifdef([AC_HELP_STRING], 13 | [AC_DEFUN([AC_LIB_ARG_WITH], [AC_ARG_WITH([$1],[[$2]],[$3],[$4])])], 14 | [AC_DEFUN([AC_][LIB_ARG_WITH], [AC_ARG_WITH([$1],[$2],[$3],[$4])])]) 15 | 16 | dnl AC_LIB_PREFIX adds to the CPPFLAGS and LDFLAGS the flags that are needed 17 | dnl to access previously installed libraries. The basic assumption is that 18 | dnl a user will want packages to use other packages he previously installed 19 | dnl with the same --prefix option. 20 | dnl This macro is not needed if only AC_LIB_LINKFLAGS is used to locate 21 | dnl libraries, but is otherwise very convenient. 22 | AC_DEFUN([AC_LIB_PREFIX], 23 | [ 24 | AC_BEFORE([$0], [AC_LIB_LINKFLAGS]) 25 | AC_REQUIRE([AC_PROG_CC]) 26 | AC_REQUIRE([AC_CANONICAL_HOST]) 27 | AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) 28 | AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) 29 | dnl By default, look in $includedir and $libdir. 30 | use_additional=yes 31 | AC_LIB_WITH_FINAL_PREFIX([ 32 | eval additional_includedir=\"$includedir\" 33 | eval additional_libdir=\"$libdir\" 34 | ]) 35 | AC_LIB_ARG_WITH([lib-prefix], 36 | [ --with-lib-prefix[=DIR] search for libraries in DIR/include and DIR/lib 37 | --without-lib-prefix don't search for libraries in includedir and libdir], 38 | [ 39 | if test "X$withval" = "Xno"; then 40 | use_additional=no 41 | else 42 | if test "X$withval" = "X"; then 43 | AC_LIB_WITH_FINAL_PREFIX([ 44 | eval additional_includedir=\"$includedir\" 45 | eval additional_libdir=\"$libdir\" 46 | ]) 47 | else 48 | additional_includedir="$withval/include" 49 | additional_libdir="$withval/$acl_libdirstem" 50 | fi 51 | fi 52 | ]) 53 | if test $use_additional = yes; then 54 | dnl Potentially add $additional_includedir to $CPPFLAGS. 55 | dnl But don't add it 56 | dnl 1. if it's the standard /usr/include, 57 | dnl 2. if it's already present in $CPPFLAGS, 58 | dnl 3. if it's /usr/local/include and we are using GCC on Linux, 59 | dnl 4. if it doesn't exist as a directory. 60 | if test "X$additional_includedir" != "X/usr/include"; then 61 | haveit= 62 | for x in $CPPFLAGS; do 63 | AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) 64 | if test "X$x" = "X-I$additional_includedir"; then 65 | haveit=yes 66 | break 67 | fi 68 | done 69 | if test -z "$haveit"; then 70 | if test "X$additional_includedir" = "X/usr/local/include"; then 71 | if test -n "$GCC"; then 72 | case $host_os in 73 | linux* | gnu* | k*bsd*-gnu) haveit=yes;; 74 | esac 75 | fi 76 | fi 77 | if test -z "$haveit"; then 78 | if test -d "$additional_includedir"; then 79 | dnl Really add $additional_includedir to $CPPFLAGS. 80 | CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }-I$additional_includedir" 81 | fi 82 | fi 83 | fi 84 | fi 85 | dnl Potentially add $additional_libdir to $LDFLAGS. 86 | dnl But don't add it 87 | dnl 1. if it's the standard /usr/lib, 88 | dnl 2. if it's already present in $LDFLAGS, 89 | dnl 3. if it's /usr/local/lib and we are using GCC on Linux, 90 | dnl 4. if it doesn't exist as a directory. 91 | if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then 92 | haveit= 93 | for x in $LDFLAGS; do 94 | AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) 95 | if test "X$x" = "X-L$additional_libdir"; then 96 | haveit=yes 97 | break 98 | fi 99 | done 100 | if test -z "$haveit"; then 101 | if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then 102 | if test -n "$GCC"; then 103 | case $host_os in 104 | linux*) haveit=yes;; 105 | esac 106 | fi 107 | fi 108 | if test -z "$haveit"; then 109 | if test -d "$additional_libdir"; then 110 | dnl Really add $additional_libdir to $LDFLAGS. 111 | LDFLAGS="${LDFLAGS}${LDFLAGS:+ }-L$additional_libdir" 112 | fi 113 | fi 114 | fi 115 | fi 116 | fi 117 | ]) 118 | 119 | dnl AC_LIB_PREPARE_PREFIX creates variables acl_final_prefix, 120 | dnl acl_final_exec_prefix, containing the values to which $prefix and 121 | dnl $exec_prefix will expand at the end of the configure script. 122 | AC_DEFUN([AC_LIB_PREPARE_PREFIX], 123 | [ 124 | dnl Unfortunately, prefix and exec_prefix get only finally determined 125 | dnl at the end of configure. 126 | if test "X$prefix" = "XNONE"; then 127 | acl_final_prefix="$ac_default_prefix" 128 | else 129 | acl_final_prefix="$prefix" 130 | fi 131 | if test "X$exec_prefix" = "XNONE"; then 132 | acl_final_exec_prefix='${prefix}' 133 | else 134 | acl_final_exec_prefix="$exec_prefix" 135 | fi 136 | acl_save_prefix="$prefix" 137 | prefix="$acl_final_prefix" 138 | eval acl_final_exec_prefix=\"$acl_final_exec_prefix\" 139 | prefix="$acl_save_prefix" 140 | ]) 141 | 142 | dnl AC_LIB_WITH_FINAL_PREFIX([statement]) evaluates statement, with the 143 | dnl variables prefix and exec_prefix bound to the values they will have 144 | dnl at the end of the configure script. 145 | AC_DEFUN([AC_LIB_WITH_FINAL_PREFIX], 146 | [ 147 | acl_save_prefix="$prefix" 148 | prefix="$acl_final_prefix" 149 | acl_save_exec_prefix="$exec_prefix" 150 | exec_prefix="$acl_final_exec_prefix" 151 | $1 152 | exec_prefix="$acl_save_exec_prefix" 153 | prefix="$acl_save_prefix" 154 | ]) 155 | 156 | dnl AC_LIB_PREPARE_MULTILIB creates 157 | dnl - a variable acl_libdirstem, containing the basename of the libdir, either 158 | dnl "lib" or "lib64" or "lib/64", 159 | dnl - a variable acl_libdirstem2, as a secondary possible value for 160 | dnl acl_libdirstem, either the same as acl_libdirstem or "lib/sparcv9" or 161 | dnl "lib/amd64". 162 | AC_DEFUN([AC_LIB_PREPARE_MULTILIB], 163 | [ 164 | dnl There is no formal standard regarding lib and lib64. 165 | dnl On glibc systems, the current practice is that on a system supporting 166 | dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under 167 | dnl $prefix/lib64 and 32-bit libraries go under $prefix/lib. We determine 168 | dnl the compiler's default mode by looking at the compiler's library search 169 | dnl path. If at least one of its elements ends in /lib64 or points to a 170 | dnl directory whose absolute pathname ends in /lib64, we assume a 64-bit ABI. 171 | dnl Otherwise we use the default, namely "lib". 172 | dnl On Solaris systems, the current practice is that on a system supporting 173 | dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under 174 | dnl $prefix/lib/64 (which is a symlink to either $prefix/lib/sparcv9 or 175 | dnl $prefix/lib/amd64) and 32-bit libraries go under $prefix/lib. 176 | AC_REQUIRE([AC_CANONICAL_HOST]) 177 | acl_libdirstem=lib 178 | acl_libdirstem2= 179 | case "$host_os" in 180 | solaris*) 181 | dnl See Solaris 10 Software Developer Collection > Solaris 64-bit Developer's Guide > The Development Environment 182 | dnl . 183 | dnl "Portable Makefiles should refer to any library directories using the 64 symbolic link." 184 | dnl But we want to recognize the sparcv9 or amd64 subdirectory also if the 185 | dnl symlink is missing, so we set acl_libdirstem2 too. 186 | AC_CACHE_CHECK([for 64-bit host], [gl_cv_solaris_64bit], 187 | [AC_EGREP_CPP([sixtyfour bits], [ 188 | #ifdef _LP64 189 | sixtyfour bits 190 | #endif 191 | ], [gl_cv_solaris_64bit=yes], [gl_cv_solaris_64bit=no]) 192 | ]) 193 | if test $gl_cv_solaris_64bit = yes; then 194 | acl_libdirstem=lib/64 195 | case "$host_cpu" in 196 | sparc*) acl_libdirstem2=lib/sparcv9 ;; 197 | i*86 | x86_64) acl_libdirstem2=lib/amd64 ;; 198 | esac 199 | fi 200 | ;; 201 | *) 202 | searchpath=`(LC_ALL=C $CC -print-search-dirs) 2>/dev/null | sed -n -e 's,^libraries: ,,p' | sed -e 's,^=,,'` 203 | if test -n "$searchpath"; then 204 | acl_save_IFS="${IFS= }"; IFS=":" 205 | for searchdir in $searchpath; do 206 | if test -d "$searchdir"; then 207 | case "$searchdir" in 208 | */lib64/ | */lib64 ) acl_libdirstem=lib64 ;; 209 | *) searchdir=`cd "$searchdir" && pwd` 210 | case "$searchdir" in 211 | */lib64 ) acl_libdirstem=lib64 ;; 212 | esac ;; 213 | esac 214 | fi 215 | done 216 | IFS="$acl_save_IFS" 217 | fi 218 | ;; 219 | esac 220 | test -n "$acl_libdirstem2" || acl_libdirstem2="$acl_libdirstem" 221 | ]) 222 | -------------------------------------------------------------------------------- /m4/manywarnings.m4: -------------------------------------------------------------------------------- 1 | # manywarnings.m4 serial 4 2 | dnl Copyright (C) 2008-2012 Free Software Foundation, Inc. 3 | dnl This file is free software; the Free Software Foundation 4 | dnl gives unlimited permission to copy and/or distribute it, 5 | dnl with or without modifications, as long as this notice is preserved. 6 | 7 | dnl From Simon Josefsson 8 | 9 | # gl_MANYWARN_COMPLEMENT(OUTVAR, LISTVAR, REMOVEVAR) 10 | # -------------------------------------------------- 11 | # Copy LISTVAR to OUTVAR except for the entries in REMOVEVAR. 12 | # Elements separated by whitespace. In set logic terms, the function 13 | # does OUTVAR = LISTVAR \ REMOVEVAR. 14 | AC_DEFUN([gl_MANYWARN_COMPLEMENT], 15 | [ 16 | gl_warn_set= 17 | set x $2; shift 18 | for gl_warn_item 19 | do 20 | case " $3 " in 21 | *" $gl_warn_item "*) 22 | ;; 23 | *) 24 | gl_warn_set="$gl_warn_set $gl_warn_item" 25 | ;; 26 | esac 27 | done 28 | $1=$gl_warn_set 29 | ]) 30 | 31 | # gl_MANYWARN_ALL_GCC(VARIABLE) 32 | # ----------------------------- 33 | # Add all documented GCC warning parameters to variable VARIABLE. 34 | # Note that you need to test them using gl_WARN_ADD if you want to 35 | # make sure your gcc understands it. 36 | AC_DEFUN([gl_MANYWARN_ALL_GCC], 37 | [ 38 | dnl First, check if -Wno-missing-field-initializers is needed. 39 | dnl -Wmissing-field-initializers is implied by -W, but that issues 40 | dnl warnings with GCC version before 4.7, for the common idiom 41 | dnl of initializing types on the stack to zero, using { 0, } 42 | AC_REQUIRE([AC_PROG_CC]) 43 | if test -n "$GCC"; then 44 | 45 | dnl First, check -W -Werror -Wno-missing-field-initializers is supported 46 | dnl with the current $CC $CFLAGS $CPPFLAGS. 47 | AC_MSG_CHECKING([whether -Wno-missing-field-initializers is supported]) 48 | AC_CACHE_VAL([gl_cv_cc_nomfi_supported], [ 49 | gl_save_CFLAGS="$CFLAGS" 50 | CFLAGS="$CFLAGS -W -Werror -Wno-missing-field-initializers" 51 | AC_COMPILE_IFELSE( 52 | [AC_LANG_PROGRAM([[]], [[]])], 53 | [gl_cv_cc_nomfi_supported=yes], 54 | [gl_cv_cc_nomfi_supported=no]) 55 | CFLAGS="$gl_save_CFLAGS"]) 56 | AC_MSG_RESULT([$gl_cv_cc_nomfi_supported]) 57 | 58 | if test "$gl_cv_cc_nomfi_supported" = yes; then 59 | dnl Now check whether -Wno-missing-field-initializers is needed 60 | dnl for the { 0, } construct. 61 | AC_MSG_CHECKING([whether -Wno-missing-field-initializers is needed]) 62 | AC_CACHE_VAL([gl_cv_cc_nomfi_needed], [ 63 | gl_save_CFLAGS="$CFLAGS" 64 | CFLAGS="$CFLAGS -W -Werror" 65 | AC_COMPILE_IFELSE( 66 | [AC_LANG_PROGRAM( 67 | [[void f (void) 68 | { 69 | typedef struct { int a; int b; } s_t; 70 | s_t s1 = { 0, }; 71 | } 72 | ]], 73 | [[]])], 74 | [gl_cv_cc_nomfi_needed=no], 75 | [gl_cv_cc_nomfi_needed=yes]) 76 | CFLAGS="$gl_save_CFLAGS" 77 | ]) 78 | AC_MSG_RESULT([$gl_cv_cc_nomfi_needed]) 79 | fi 80 | fi 81 | 82 | gl_manywarn_set= 83 | for gl_manywarn_item in \ 84 | -Wall \ 85 | -W \ 86 | -Wformat-y2k \ 87 | -Wformat-nonliteral \ 88 | -Wformat-security \ 89 | -Winit-self \ 90 | -Wmissing-include-dirs \ 91 | -Wswitch-default \ 92 | -Wswitch-enum \ 93 | -Wunused \ 94 | -Wunknown-pragmas \ 95 | -Wstrict-aliasing \ 96 | -Wstrict-overflow \ 97 | -Wsystem-headers \ 98 | -Wfloat-equal \ 99 | -Wtraditional \ 100 | -Wtraditional-conversion \ 101 | -Wdeclaration-after-statement \ 102 | -Wundef \ 103 | -Wshadow \ 104 | -Wunsafe-loop-optimizations \ 105 | -Wpointer-arith \ 106 | -Wbad-function-cast \ 107 | -Wc++-compat \ 108 | -Wcast-qual \ 109 | -Wcast-align \ 110 | -Wwrite-strings \ 111 | -Wconversion \ 112 | -Wsign-conversion \ 113 | -Wlogical-op \ 114 | -Waggregate-return \ 115 | -Wstrict-prototypes \ 116 | -Wold-style-definition \ 117 | -Wmissing-prototypes \ 118 | -Wmissing-declarations \ 119 | -Wmissing-noreturn \ 120 | -Wmissing-format-attribute \ 121 | -Wpacked \ 122 | -Wpadded \ 123 | -Wredundant-decls \ 124 | -Wnested-externs \ 125 | -Wunreachable-code \ 126 | -Winline \ 127 | -Winvalid-pch \ 128 | -Wlong-long \ 129 | -Wvla \ 130 | -Wvolatile-register-var \ 131 | -Wdisabled-optimization \ 132 | -Wstack-protector \ 133 | -Woverlength-strings \ 134 | -Wbuiltin-macro-redefined \ 135 | -Wmudflap \ 136 | -Wpacked-bitfield-compat \ 137 | -Wsync-nand \ 138 | ; do 139 | gl_manywarn_set="$gl_manywarn_set $gl_manywarn_item" 140 | done 141 | # The following are not documented in the manual but are included in 142 | # output from gcc --help=warnings. 143 | for gl_manywarn_item in \ 144 | -Wattributes \ 145 | -Wcoverage-mismatch \ 146 | -Wunused-macros \ 147 | ; do 148 | gl_manywarn_set="$gl_manywarn_set $gl_manywarn_item" 149 | done 150 | # More warnings from gcc 4.6.2 --help=warnings. 151 | for gl_manywarn_item in \ 152 | -Wabi \ 153 | -Wcpp \ 154 | -Wdeprecated \ 155 | -Wdeprecated-declarations \ 156 | -Wdiv-by-zero \ 157 | -Wdouble-promotion \ 158 | -Wendif-labels \ 159 | -Wextra \ 160 | -Wformat-contains-nul \ 161 | -Wformat-extra-args \ 162 | -Wformat-zero-length \ 163 | -Wformat=2 \ 164 | -Wmultichar \ 165 | -Wnormalized=nfc \ 166 | -Woverflow \ 167 | -Wpointer-to-int-cast \ 168 | -Wpragmas \ 169 | -Wsuggest-attribute=const \ 170 | -Wsuggest-attribute=noreturn \ 171 | -Wsuggest-attribute=pure \ 172 | -Wtrampolines \ 173 | ; do 174 | gl_manywarn_set="$gl_manywarn_set $gl_manywarn_item" 175 | done 176 | 177 | # Disable the missing-field-initializers warning if needed 178 | if test "$gl_cv_cc_nomfi_needed" = yes; then 179 | gl_manywarn_set="$gl_manywarn_set -Wno-missing-field-initializers" 180 | fi 181 | 182 | $1=$gl_manywarn_set 183 | ]) 184 | -------------------------------------------------------------------------------- /m4/warnings.m4: -------------------------------------------------------------------------------- 1 | # warnings.m4 serial 7 2 | dnl Copyright (C) 2008-2012 Free Software Foundation, Inc. 3 | dnl This file is free software; the Free Software Foundation 4 | dnl gives unlimited permission to copy and/or distribute it, 5 | dnl with or without modifications, as long as this notice is preserved. 6 | 7 | dnl From Simon Josefsson 8 | 9 | # gl_AS_VAR_APPEND(VAR, VALUE) 10 | # ---------------------------- 11 | # Provide the functionality of AS_VAR_APPEND if Autoconf does not have it. 12 | m4_ifdef([AS_VAR_APPEND], 13 | [m4_copy([AS_VAR_APPEND], [gl_AS_VAR_APPEND])], 14 | [m4_define([gl_AS_VAR_APPEND], 15 | [AS_VAR_SET([$1], [AS_VAR_GET([$1])$2])])]) 16 | 17 | 18 | # gl_COMPILER_OPTION_IF(OPTION, [IF-SUPPORTED], [IF-NOT-SUPPORTED], 19 | # [PROGRAM = AC_LANG_PROGRAM()]) 20 | # ----------------------------------------------------------------- 21 | # Check if the compiler supports OPTION when compiling PROGRAM. 22 | # 23 | # FIXME: gl_Warn must be used unquoted until we can assume Autoconf 24 | # 2.64 or newer. 25 | AC_DEFUN([gl_COMPILER_OPTION_IF], 26 | [AS_VAR_PUSHDEF([gl_Warn], [gl_cv_warn_[]_AC_LANG_ABBREV[]_$1])dnl 27 | AS_VAR_PUSHDEF([gl_Flags], [_AC_LANG_PREFIX[]FLAGS])dnl 28 | AC_CACHE_CHECK([whether _AC_LANG compiler handles $1], m4_defn([gl_Warn]), [ 29 | gl_save_compiler_FLAGS="$gl_Flags" 30 | gl_AS_VAR_APPEND(m4_defn([gl_Flags]), [" $1"]) 31 | AC_COMPILE_IFELSE([m4_default([$4], [AC_LANG_PROGRAM([])])], 32 | [AS_VAR_SET(gl_Warn, [yes])], 33 | [AS_VAR_SET(gl_Warn, [no])]) 34 | gl_Flags="$gl_save_compiler_FLAGS" 35 | ]) 36 | AS_VAR_IF(gl_Warn, [yes], [$2], [$3]) 37 | AS_VAR_POPDEF([gl_Flags])dnl 38 | AS_VAR_POPDEF([gl_Warn])dnl 39 | ]) 40 | 41 | 42 | # gl_WARN_ADD(OPTION, [VARIABLE = WARN_CFLAGS], 43 | # [PROGRAM = AC_LANG_PROGRAM()]) 44 | # --------------------------------------------- 45 | # Adds parameter to WARN_CFLAGS if the compiler supports it when 46 | # compiling PROGRAM. For example, gl_WARN_ADD([-Wparentheses]). 47 | # 48 | # If VARIABLE is a variable name, AC_SUBST it. 49 | AC_DEFUN([gl_WARN_ADD], 50 | [gl_COMPILER_OPTION_IF([$1], 51 | [gl_AS_VAR_APPEND(m4_if([$2], [], [[WARN_CFLAGS]], [[$2]]), [" $1"])], 52 | [], 53 | [$3]) 54 | m4_ifval([$2], 55 | [AS_LITERAL_IF([$2], [AC_SUBST([$2])])], 56 | [AC_SUBST([WARN_CFLAGS])])dnl 57 | ]) 58 | 59 | # Local Variables: 60 | # mode: autoconf 61 | # End: 62 | -------------------------------------------------------------------------------- /mac.mk: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Yubico AB 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are 6 | # met: 7 | # 8 | # * Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above 12 | # copyright notice, this list of conditions and the following 13 | # disclaimer in the documentation and/or other materials provided 14 | # with the distribution. 15 | # 16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | YKPERS_VERSION=1.18.1 29 | YKCLIENT_VERSION=2.15 30 | CFLAGS="-mmacosx-version-min=10.6 -arch i386 -arch x86_64" 31 | CHECK=check 32 | ROOT_DIR="com.yubico.pam_yubico" 33 | LICENSE_DIR="$(ROOT_DIR)/share/pam_yubico/licenses" 34 | INSTALLER_IDENTITY:="Developer ID Installer" 35 | ifeq ($(SIGN), sign) 36 | SIGNING=--sign $(INSTALLER_IDENTITY) --timestamp 37 | endif 38 | 39 | all: usage doit 40 | 41 | .PHONY: usage 42 | usage: 43 | @if test -z "$(VERSION)"; then \ 44 | echo "Must supply VERSION"; \ 45 | exit 1; \ 46 | fi 47 | 48 | doit: 49 | DIR=`mktemp -d $(PWD)/pkg.XXXXXX` && \ 50 | cd $$DIR && \ 51 | cp ../ykpers-$(YKPERS_VERSION)-mac.zip . || \ 52 | curl -L -O "https://developers.yubico.com/yubikey-personalization/Releases/ykpers-$(YKPERS_VERSION)-mac.zip" && \ 53 | mkdir -p $(LICENSE_DIR) && \ 54 | mkdir lib && cd lib && \ 55 | unzip ../ykpers-$(YKPERS_VERSION)-mac.zip && \ 56 | rm -rf lib/*.la && \ 57 | cd .. && \ 58 | cp ../ykclient-$(YKCLIENT_VERSION).tar.gz || \ 59 | curl -L -O "https://developers.yubico.com/yubico-c-client/Releases/ykclient-$(YKCLIENT_VERSION).tar.gz" && \ 60 | tar xfz ykclient-$(YKCLIENT_VERSION).tar.gz && \ 61 | cd ykclient-$(YKCLIENT_VERSION) && \ 62 | CFLAGS=$(CFLAGS) PKG_CONFIG_PATH=$$DIR/lib/lib/pkgconfig ./configure --prefix=$$DIR/lib/ && \ 63 | make $(CHECK) install && \ 64 | cp COPYING $$DIR/$(LICENSE_DIR)/yubico-c-client.txt && \ 65 | cd .. && \ 66 | mkdir -p $$DIR/$(ROOT_DIR)/lib && \ 67 | LIBS="libjson-c.2.dylib libykclient.3.dylib libykpers-1.1.dylib libyubikey.0.dylib" && \ 68 | for lib in $$LIBS; do \ 69 | install_name_tool -id @loader_path/../$$lib $$DIR/lib/lib/$$lib && \ 70 | install_name_tool -change @executable_path/../lib/libyubikey.0.dylib @loader_path/libyubikey.0.dylib $$DIR/lib/lib/$$lib && \ 71 | install_name_tool -change @executable_path/../lib/libjson-c.2.dylib @loader_path/libjson-c.2.dylib $$DIR/lib/lib/$$lib && \ 72 | cp $$DIR/lib/lib/$$lib $$DIR/$(ROOT_DIR)/lib/ ; \ 73 | done && \ 74 | cp ../pam_yubico-$(VERSION).tar.gz . || \ 75 | curl -L -O "https://developers.yubico.com/yubico-pam/Releases/pam_yubico-$(VERSION).tar.gz" && \ 76 | tar xfz pam_yubico-$(VERSION).tar.gz && \ 77 | cd pam_yubico-$(VERSION)/ && \ 78 | YKPERS_CFLAGS=-I$$DIR/lib/include/ykpers-1 YKPERS_LIBS="-L$$DIR/lib/lib/ -lykpers-1" CFLAGS=$(CFLAGS) PKG_CONFIG_PATH=$$DIR/lib/lib/pkgconfig ./configure --prefix=$$DIR/$(ROOT_DIR)/ --with-libyubikey-prefix=$$DIR/lib/ --with-libykclient-prefix=$$DIR/lib/ && \ 79 | make install && \ 80 | install_name_tool -change @executable_path/../lib/libyubikey.0.dylib @loader_path/../libyubikey.0.dylib $$DIR/$(ROOT_DIR)/lib/security/pam_yubico.so && \ 81 | install_name_tool -change @executable_path/../lib/libykpers-1.1.dylib @loader_path/../libykpers-1.1.dylib $$DIR/$(ROOT_DIR)/lib/security/pam_yubico.so && \ 82 | cp COPYING $$DIR/$(LICENSE_DIR)/yubico-pam.txt && \ 83 | cd ../.. && \ 84 | rm $$DIR/$(ROOT_DIR)/lib/security/*.la && \ 85 | cp $$DIR/lib/licenses/* $$DIR/$(LICENSE_DIR) && \ 86 | productbuild --root $$DIR/$(ROOT_DIR)/ /usr/local/ --version $(VERSION) pam_yubico-$(VERSION).pkg $(SIGNING) && \ 87 | rm -rf $$DIR 88 | -------------------------------------------------------------------------------- /pam_yubico.8.txt: -------------------------------------------------------------------------------- 1 | PAM_YUBICO(8) 2 | ============= 3 | :doctype: manpage 4 | :man source: yubico-pam 5 | :man manual: Yubico PAM Module Manual 6 | 7 | == NAME 8 | pam_yubico - Module for YubiKey authentication 9 | 10 | == SYNOPSIS 11 | *pam_yubico* [...] 12 | 13 | == DESCRIPTION 14 | The module is for authentication of YubiKeys, either with online validation of OTP, or offline validation with HMAC-SHA1 challenge-response. 15 | 16 | == OPTIONS 17 | *debug*:: 18 | Turns on debugging. 19 | 20 | *debug_file*=_file_:: 21 | File name to write debug to, the file must exist and be a regular file. Defaults to stdout. 22 | 23 | *mode=*[_client_|_challenge-response_]:: 24 | Mode of operation, client for OTP validation and challenge-response for challenge-response validation. Defaults to client. 25 | 26 | *authfile*=_file_:: 27 | Location of the file that holds the mappings of YubiKey token IDs to user names. The format is username:first_public_id:second_public_id:... Default location of the file is $HOME/.yubico/authorized_yubikeys. 28 | 29 | *id*=_id_:: 30 | Your API client identity for the validation server. 31 | 32 | *key*=_key_:: 33 | Your client key in base64 format. The client key is also known as API key, and provides integrity in the communication between the client (you) and the validation server. If you want to get one for use with the default YubiCloud service, please go to: https://upgrade.yubico.com/getapikey/ 34 | 35 | *alwaysok*:: 36 | Succeed with all authentication attempts (*dangerous*, presentation mode). 37 | 38 | *try_first_pass*:: 39 | 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. 40 | 41 | *use_first_pass*:: 42 | 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. 43 | 44 | *always_prompt*:: 45 | If set, don't attempt to do a lookup to determine if the user has a YubiKey configured but instead prompt for one no matter what. This is useful in the case where ldap_bind_as_user is enabled but this module is being used to read the user's password (in a YubiKey+OTP auth scenario). 46 | 47 | *nullok*:: 48 | Don’t fail when there are no tokens declared for the user in the authorization mapping files or in LDAP. This can be used to make YubiKey authentication optional unless the user has associated tokens. 49 | 50 | *ldap_starttls*:: 51 | If set, issue a STARTTLS command to the LDAP connection before attempting to bind to it. This is a common setup for servers that only listen on port 389 but still require TLS. 52 | 53 | *ldap_bind_as_user*:: 54 | Use the user logging in to bind to ldap. This will use the password provided by the user via PAM. If this is set, ldapdn and uid_attr must also be set. Enabling this will cause ldap_bind_user and ldap_bind_password to be ignored. 55 | 56 | *urllist*=_list_:: 57 | List of URL templates to be used. This is set by calling ykclient_set_url_bases. 58 | The list should be in the format: 59 | https://api1.example.com/wsapi/2.0/verify;https://api2.example.com/wsapi/2.0/verify 60 | 61 | *url*=_url_:: 62 | This option should not be used, please use the urllist option instead. Set the URL template to use, this is set by calling ykclient_set_url_template. The URL should be set in the format 63 | https://api.example.com/wsapi/2.0/verify?id=%d&otp=%s 64 | 65 | *capath*=_path_:: 66 | The path where X509 certificates are stored. This is required if 'https' or 'ldaps' are used in 'url' and 'ldap_uri', respectively. 67 | 68 | *cainfo*=_file_:: 69 | Option to allow for usage of a CA bundle instead of path. 70 | 71 | *proxy*=_proxy_:: 72 | The proxy to connect to the validation server. Valid schemes are http://, https://, socks4://, socks4a://, socks5:// or socks5h://. Socks5h asks the proxy to do the DNS resolving. If no scheme or port is specified HTTP proxy port 1080 will be used. Example: socks5h://user:pass@10.10.0.1:1080 73 | 74 | *verbose_otp*:: 75 | Show the One-Time Password when it is entered, i.e. to enable terminal echo of entered characters. You are advised to not use this, if you are using two factor authentication because that will display your password on the screen. This requires the service using the PAM module to display custom fields. This option can not be used with OpenSSH. 76 | 77 | *ldap_uri*=_uri_:: 78 | The LDAP server URI (e.g. ldap://localhost). 79 | 80 | *ldap_server*=_server_:: 81 | The LDAP server host (default LDAP port is used). *Deprecated. Use 'ldap_uri' instead.* 82 | 83 | *ldapdn*=_dn_:: 84 | The distinguished name (DN) where the users are stored (eg: ou=users,dc=domain,dc=com). If 'ldap_filter' is used this is the base from which the subtree search will be performed. 85 | 86 | *ldap_clientcertfile*=_clientcertfile_:: 87 | The path to a client cert file to use when talking to the LDAP server. Note this requires 'ldap_clientkeyfile' to be set as well. 88 | 89 | *ldap_clientkeyfile*=_clientkeyfile_:: 90 | The path to a key to be used with the client cert when talking to the LDAP server. Note this requires 'ldap_clientcertfile' to be set as well. 91 | 92 | *user_attr*=_attr_:: 93 | The LDAP attribute used to store user names (eg:cn). 94 | 95 | *yubi_attr*=_attr_:: 96 | The LDAP attribute used to store the YubiKey ID. 97 | 98 | *yubi_attr_prefix*=_prefix_:: 99 | The prefix of the LDAP attribute's value, in case of a generic attribute, used to store several types of IDs. 100 | 101 | *token_id_length*=_length_:: 102 | Length of ID prefixing the OTP (this is 12 if using the YubiCloud). 103 | 104 | *ldap_bind_user*=_user_:: 105 | The user to attempt a LDAP bind as. 106 | 107 | *ldap_bind_password*=_password_:: 108 | The password to use on LDAP bind. 109 | 110 | *ldap_filter*=_filter_:: 111 | A LDAP filter to use for attempting to find the correct object in LDAP. In this string `%u` will be replaced with the username. 112 | 113 | *ldap_cacertfile*=_cacertfile_:: 114 | CA certitificate file for the LDAP connection. 115 | 116 | *chalresp_path*=_path_:: 117 | Path of a system-wide directory where challenge-response files can be found for users. Default location is `$HOME/.yubico/`. 118 | 119 | *mysql_server*=_mysqlserver_:: 120 | Hostname/Adress of mysql server. Example 10.0.0.1 121 | 122 | *mysql_port*=_mysqlport_:: 123 | Network port of mysql server. 124 | 125 | *mysql_user*=_mysqluser_:: 126 | User for accessing to the database. Strongly recommended to use a specific user with read only access. 127 | 128 | *mysql_password*=_mysqlpassword_:: 129 | Mysql password associated to the user. 130 | 131 | *mysql_database*=_mysqldatabase_:: 132 | the name of the database. Example : otp 133 | 134 | == EXAMPLES 135 | 136 | auth sufficient pam_yubico.so id=16 debug 137 | 138 | auth required pam_yubico.so mode=challenge-response 139 | 140 | auth required pam_yubico.so id=16 ldap_uri=ldaps://ldap.example.com ldap_filter=(uid=%u) yubi_attr=yubiKeyId 141 | 142 | == FILES 143 | 144 | *$HOME/.yubico/authorized_yubikeys*:: 145 | If *authfile* is not set, this file is used for the mapping between YubiKey public ID and in _client_ mode. 146 | 147 | *$HOME/.yubico/challenge*:: 148 | *$HOME/.yubico/challenge-_serial_number_*:: 149 | If *chalresp_path* is not set, these files are used to hold next challenge and expected response for the user in _challenge-response_ mode. If *chalresp_path* is set the filename will be _username_ instead of _challenge_. 150 | 151 | == BUGS 152 | Report yubico-pam bugs in the issue tracker: https://github.com/Yubico/yubico-pam/issues 153 | 154 | == SEE ALSO 155 | *ykpamcfg*(1), *pam*(7) 156 | 157 | The yubico-pam home page: https://developers.yubico.com/yubico-pam/ 158 | 159 | YubiKeys can be obtained from Yubico: http://www.yubico.com/ 160 | -------------------------------------------------------------------------------- /tests/Makefile.am: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Yubico AB 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are 6 | # met: 7 | # 8 | # * Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above 12 | # copyright notice, this list of conditions and the following 13 | # disclaimer in the documentation and/or other materials provided 14 | # with the distribution. 15 | # 16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | # Self tests. 29 | 30 | AM_LDFLAGS = -no-install 31 | AM_CFLAGS=-I$(srcdir)/.. $(WARN_CFLAGS) 32 | AM_CPPFLAGS = @YKPERS_CFLAGS@ 33 | util_test_LDADD = ../libpam_util.la 34 | pam_test_LDADD = ../libpam_real.la ../libpam_util.la @LTLIBYUBIKEY@ @LTLIBYKCLIENT@ @LIBLDAP@ 35 | 36 | pam_test_CPPFLAGS = -DSRCDIR=\"$(srcdir)\" 37 | TESTS_ENVIRONMENT = export LDAPNOINIT=1; 38 | 39 | check_PROGRAMS = util_test pam_test 40 | TESTS = $(check_PROGRAMS) 41 | 42 | if ENABLE_COV 43 | AM_LDFLAGS += --coverage 44 | endif 45 | -------------------------------------------------------------------------------- /tests/aux/auth_mapping.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO yubikey_mappings (otp_id, username) VALUES ('vvincredible', 'foo'); 2 | INSERT INTO yubikey_mappings (otp_id, username) VALUES ('cccccccfhcbe', 'test'); 3 | INSERT INTO yubikey_mappings (otp_id, username) VALUES ('ccccccbchvth', 'test'); 4 | -------------------------------------------------------------------------------- /tests/aux/authfile: -------------------------------------------------------------------------------- 1 | foo:vvincredible 2 | test:cccccccfhcbe:ccccccbchvth: 3 | -------------------------------------------------------------------------------- /tests/aux/build-and-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | autoreconf -i 6 | 7 | if [ "x$TRAVIS_OS_NAME" != "xosx" ]; then 8 | sudo add-apt-repository -y ppa:yubico/stable 9 | sudo apt-get update -qq || true 10 | sudo apt-get install -qq -y --no-install-recommends libykclient-dev libpam0g-dev libyubikey-dev asciidoc docbook-xsl xsltproc libxml2-utils $EXTRA 11 | else 12 | brew update 13 | brew install pkg-config 14 | brew install libtool 15 | brew install asciidoc 16 | brew install docbook-xsl 17 | brew install libyubikey 18 | brew install ykclient 19 | brew install ykpers 20 | brew install mysql-connector-c #Mysql 21 | cpanp install Net::LDAP::Server 22 | 23 | # this is required so asciidoc can find the xml catalog 24 | export XML_CATALOG_FILES=/usr/local/etc/xml/catalog 25 | fi 26 | 27 | set -e 28 | 29 | if [ ! -z $MYSQL_PORT ]; then 30 | CFLAGS="-DTEST_MYSQL_PORT='\"${MYSQL_PORT}\"' -DRUN_MYSQL_TESTS" ./configure $CONFIGURE_ARGS $COVERAGE 31 | else 32 | ./configure $CONFIGURE_ARGS $COVERAGE 33 | fi 34 | 35 | make check check-doc-dist 36 | if [ "x$COVERAGE" != "x" ]; then 37 | gem install coveralls-lcov 38 | coveralls-lcov coverage/app2.info 39 | fi 40 | -------------------------------------------------------------------------------- /tests/aux/ldap.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # Copyright (c) 2015 Yubico AB 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are 8 | # met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # 13 | # * Redistributions in binary form must reproduce the above 14 | # copyright notice, this list of conditions and the following 15 | # disclaimer in the documentation and/or other materials provided 16 | # with the distribution. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | use strict; 31 | use warnings; 32 | 33 | package PamServer; 34 | use Net::LDAP::Server; 35 | use Net::LDAP::Constant qw/LDAP_SUCCESS/; 36 | 37 | use base 'Net::LDAP::Server'; 38 | 39 | use constant RESULT_OK => { 40 | 'matchedDN' => '', 41 | 'errorMessage' => '', 42 | 'resultCode' => LDAP_SUCCESS 43 | }; 44 | 45 | my %objects = ( 46 | 'base=uid=foo,ou=users,dc=example,dc=com' => {keys => ['vvincredible']}, 47 | 'base=uid=test,ou=users,dc=example,dc=com' => {keys => ['cccccccfhcbe', 'ccccccbchvth']}, 48 | 'base=uid=nokeys,ou=users,dc=example,dc=com' => {keys => []}, 49 | 'sub:base=:(uid=test)' => {keys => ['cccccccfhcbe', 'ccccccbchvth'], dn => 'uid=test,out=users,dc=example,dc=com'}, 50 | ); 51 | 52 | sub bind { 53 | my $self = shift; 54 | my $reqData = shift; 55 | return RESULT_OK; 56 | } 57 | 58 | sub search { 59 | my $self = shift; 60 | my $reqData = shift; 61 | my $id; 62 | my $base; 63 | if($reqData->{'scope'} == 0) { 64 | $base = $reqData->{'baseObject'}; 65 | $id = $objects{'base=' . $base}; 66 | } elsif($reqData->{'scope'} == 2) { 67 | my $match = $reqData->{'filter'}->{'equalityMatch'}; 68 | $id = $objects{'sub:base=' . $reqData->{'baseObject'} . ':(' . $match->{'attributeDesc'} . '=' . $match->{'assertionValue'} . ')'}; 69 | $base = $id->{'dn'}; 70 | } 71 | warn "ldap search with " . $reqData->{'scope'}; 72 | my @entries; 73 | if($id) { 74 | my $entry = Net::LDAP::Entry->new; 75 | $entry->dn($base); 76 | $entry->add(objectClass => [ "person" ]); 77 | $entry->add(yubiKeyId => $id->{'keys'}); 78 | push @entries, $entry; 79 | } 80 | return RESULT_OK, @entries; 81 | } 82 | 83 | package main; 84 | 85 | use IO::Socket::INET; 86 | use IO::Select; 87 | 88 | my $port = shift; 89 | die "no port specified" unless $port; 90 | 91 | my $sock = IO::Socket::INET->new( 92 | Proto => 'tcp', 93 | LocalAddr => "localhost:$port", 94 | Reuse => 1 95 | ) or die "$!"; 96 | $sock->listen(); 97 | 98 | my $sel = IO::Select->new($sock); 99 | my %handlers; 100 | 101 | warn "LDAP mockup started"; 102 | 103 | while (my @ready = $sel->can_read) { 104 | foreach my $fh (@ready) { 105 | if ($fh == $sock) { 106 | my $psock = $sock->accept; 107 | $sel->add($psock); 108 | $handlers{*$psock} = PamServer->new($psock); 109 | } else { 110 | my $result = $handlers{*$fh}->handle; 111 | if ($result) { 112 | $sel->remove($fh); 113 | $fh->close; 114 | delete $handlers{*$fh}; 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/aux/ykval.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # Copyright (c) 2015 Yubico AB 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are 8 | # met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # 13 | # * Redistributions in binary form must reproduce the above 14 | # copyright notice, this list of conditions and the following 15 | # disclaimer in the documentation and/or other materials provided 16 | # with the distribution. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | use IO::Socket::INET; 31 | 32 | use strict; 33 | use warnings; 34 | 35 | my %otps = ( 36 | 'vvincredibletrerdegkkrkkneieultcjdghrejjbckh' => 'OK', 37 | 'vvincrediblltrerdegkkrkkneieultcjdghrejjbckh' => 'OK', 38 | 'ccccccbchvthlivuitriujjifivbvtrjkjfirllluurj' => 'OK', 39 | ); 40 | 41 | my $port = shift; 42 | die "no port specified" unless $port; 43 | 44 | my $socket = new IO::Socket::INET ( 45 | LocalHost => '127.0.0.1', 46 | LocalPort => $port, 47 | Proto => 'tcp', 48 | Listen => 10, 49 | Reuse => 1 50 | ) or die "Oops: $! \n"; 51 | 52 | warn "YKVAL mockup started on $port"; 53 | 54 | while(1) { 55 | my $clientsocket = $socket->accept(); 56 | my $clientdata = <$clientsocket>; 57 | my $ret = "HTTP/1.1 200 OK\n\n"; 58 | $ret .= "h=ZrU7UfjwazJVf5ay1P/oC3XCQlI=\n"; 59 | 60 | if($clientdata =~ m/nonce=([a-zA-Z0-9]+).*otp=([cbdefghijklnrtuv]+)/) { 61 | my $nonce = $1; 62 | my $otp = $2; 63 | warn "validation for $otp (on port $port)"; 64 | if($otps{$otp}) { 65 | my $status = $otps{$otp}; 66 | $ret .= "nonce=$nonce\n"; 67 | $ret .= "otp=$otp\n"; 68 | $ret .= "status=$status"; 69 | } else { 70 | $ret .= "status=BAD_OTP"; 71 | } 72 | } else { 73 | $ret .= "status=MISSING_PARAMETER"; 74 | } 75 | print $clientsocket "$ret\n"; 76 | close $clientsocket; 77 | } 78 | -------------------------------------------------------------------------------- /tests/pam_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Yubico AB 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 12 | * * Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following 14 | * disclaimer in the documentation and/or other materials provided 15 | * with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | #include 38 | 39 | #include 40 | 41 | #include 42 | #ifdef HAVE_PAM_MODUTIL_DROP_PRIV 43 | #include 44 | #else 45 | #include 46 | struct pam_modutil_privs { 47 | int noop; 48 | }; 49 | #endif 50 | 51 | int 52 | pam_sm_authenticate (pam_handle_t * pamh, 53 | int flags, int argc, const char **argv); 54 | 55 | #define YKVAL_PORT1 "17502" 56 | #define YKVAL_PORT2 "30559" 57 | #define LDAP_PORT "52825" 58 | 59 | #ifndef TEST_MYSQL_PORT 60 | #define TEST_MYSQL_PORT "3306" 61 | #endif 62 | 63 | #define YKVAL SRCDIR"/aux/ykval.pl" 64 | #define LDAP SRCDIR"/aux/ldap.pl" 65 | #define AUTHFILE SRCDIR"/aux/authfile" 66 | 67 | static struct data { 68 | const char user[255]; 69 | const char otp[255]; 70 | } _data[] = { 71 | {"foo", "vvincredibletrerdegkkrkkneieultcjdghrejjbckh"}, 72 | {"bar", "vvincredibletrerdegkkrkkneieultcjdghrejjbckh"}, 73 | {"foo", "vvincrediblltrerdegkkrkkneieultcjdghrejjbckh"}, 74 | {"foo", "vvincredibletrerdegkkrkkneieultcjdghrejjbckl"}, 75 | {"test", "ccccccbchvthlivuitriujjifivbvtrjkjfirllluurj"}, 76 | {"foo", ""}, 77 | {"bar", ""}, 78 | {"nokeys", ""}, 79 | {"foo", "testpasswordvvincredibletrerdegkkrkkneieultcjdghrejjbckh"}, 80 | {"foo", "testpassword"}, 81 | {"bar", "testpassword"}, 82 | }; 83 | 84 | 85 | static const char *ldap_cfg[] = { 86 | "id=1", 87 | "urllist=http://localhost:"YKVAL_PORT2"/wsapi/2/verify;http://localhost:"YKVAL_PORT1"/wsapi/2/verify", 88 | "ldap_uri=ldap://localhost:"LDAP_PORT, 89 | "ldapdn=ou=users,dc=example,dc=com", 90 | "user_attr=uid", 91 | "yubi_attr=yubiKeyId", 92 | "debug" 93 | }; 94 | 95 | static const char *ldap_cfg2[] = { 96 | "id=1", 97 | "urllist=http://localhost:"YKVAL_PORT1"/wsapi/2/verify;http://localhost:"YKVAL_PORT2"/wsapi/2/verify", 98 | "ldap_uri=ldap://localhost:"LDAP_PORT, 99 | "ldap_filter=(uid=%u)", 100 | "yubi_attr=yubiKeyId", 101 | "debug" 102 | }; 103 | 104 | static const char *mysql_cfg[] = { 105 | "id=1", 106 | "urllist=http://localhost:"YKVAL_PORT1"/wsapi/2/verify", 107 | "mysql_server=127.0.0.1", 108 | "mysql_port="TEST_MYSQL_PORT, 109 | "mysql_user=user", 110 | "mysql_password=password", 111 | "mysql_database=otp", 112 | "debug" 113 | }; 114 | 115 | static const struct data *test_get_data(void *id) { 116 | return &_data[(long)id]; 117 | } 118 | 119 | #ifdef OPENPAM 120 | const char * pam_strerror(const pam_handle_t *pamh, int errnum) { 121 | #else 122 | const char * pam_strerror(pam_handle_t *pamh, int errnum) { 123 | #endif 124 | fprintf(stderr, "in pam_strerror()\n"); 125 | return "error"; 126 | } 127 | 128 | int pam_set_data(pam_handle_t *pamh, const char *module_data_name, void *data, 129 | void (*cleanup)(pam_handle_t *pamh, void *data, int error_status)) { 130 | fprintf(stderr, "in pam_set_data() %s\n", module_data_name); 131 | return PAM_SUCCESS; 132 | } 133 | 134 | int pam_get_data(const pam_handle_t *pamh, const char *module_data_name, const void **data) { 135 | fprintf(stderr, "in pam_get_data() %s\n", module_data_name); 136 | return PAM_SUCCESS; 137 | } 138 | 139 | #ifdef OPENPAM 140 | int pam_get_user(pam_handle_t *pamh, const char **user, const char *prompt) { 141 | #else 142 | int pam_get_user(const pam_handle_t *pamh, const char **user, const char *prompt) { 143 | #endif 144 | fprintf(stderr, "in pam_get_user()\n"); 145 | *user = test_get_data((void*)pamh)->user; 146 | return PAM_SUCCESS; 147 | } 148 | 149 | static int conv_func(int num_msg, const struct pam_message **msg, 150 | struct pam_response **resp, void *appdata_ptr) { 151 | struct pam_response *reply; 152 | fprintf(stderr, "in conv_func()\n"); 153 | if(num_msg != 1) { 154 | return PAM_CONV_ERR; 155 | } 156 | 157 | reply = malloc(sizeof(struct pam_response)); 158 | reply->resp = strdup(test_get_data(appdata_ptr)->otp); 159 | *resp = reply; 160 | return PAM_SUCCESS; 161 | } 162 | 163 | static struct pam_conv pam_conversation = { 164 | conv_func, 165 | NULL, 166 | }; 167 | 168 | int pam_get_item(const pam_handle_t *pamh, int item_type, const void **item) { 169 | fprintf(stderr, "in pam_get_item() %d for %d\n", item_type, (int)(uintptr_t)pamh); 170 | if(item_type == PAM_CONV) { 171 | pam_conversation.appdata_ptr = (void*)pamh; 172 | *item = &pam_conversation; 173 | } 174 | if(item_type == PAM_AUTHTOK && pamh >= (pam_handle_t*)8) { 175 | *item = (void*)_data[(int)(uintptr_t)pamh].otp; 176 | } 177 | return PAM_SUCCESS; 178 | } 179 | 180 | int pam_modutil_drop_priv(pam_handle_t *pamh, struct pam_modutil_privs *p, 181 | const struct passwd *pw) { 182 | fprintf(stderr, "in pam_modutil_drop_priv()\n"); 183 | return PAM_SUCCESS; 184 | } 185 | 186 | int pam_modutil_regain_priv(pam_handle_t *pamh, struct pam_modutil_privs *p) { 187 | fprintf(stderr, "in pam_modutil_regain_priv()\n"); 188 | return PAM_SUCCESS; 189 | } 190 | 191 | int pam_set_item(pam_handle_t *pamh, int item_type, const void *item) { 192 | fprintf(stderr, "in pam_set_item()\n"); 193 | return PAM_SUCCESS; 194 | } 195 | 196 | static int test_authenticate1(void) { 197 | const char *cfg[] = { 198 | "id=1", 199 | "url=http://localhost:"YKVAL_PORT1"/wsapi/2/verify?id=%d&otp=%s", 200 | "authfile="AUTHFILE, 201 | "debug", 202 | }; 203 | return pam_sm_authenticate((pam_handle_t *)0, 0, sizeof(cfg) / sizeof(char*), cfg); 204 | } 205 | 206 | static int test_authenticate2(void) { 207 | const char *cfg[] = { 208 | "id=1", 209 | "urllist=http://localhost:"YKVAL_PORT1"/wsapi/2/verify;http://localhost:"YKVAL_PORT2"/wsapi/2/verify", 210 | "authfile="AUTHFILE, 211 | "debug", 212 | }; 213 | return pam_sm_authenticate((pam_handle_t *)0, 0, sizeof(cfg) / sizeof(char*), cfg); 214 | } 215 | 216 | static int test_authenticate3(void) { 217 | const char *cfg[] = { 218 | "id=1", 219 | "urllist=http://localhost:"YKVAL_PORT1"/wsapi/2/verify", 220 | "authfile="AUTHFILE, 221 | "debug", 222 | }; 223 | return pam_sm_authenticate((pam_handle_t *)4, 0, sizeof(cfg) / sizeof(char*), cfg); 224 | } 225 | 226 | static int test_authenticate4(void) { 227 | const char *cfg[] = { 228 | "id=1", 229 | "urllist=http://localhost:"YKVAL_PORT1"/wsapi/2/verify;http://localhost:"YKVAL_PORT2"/wsapi/2/verify", 230 | "authfile="AUTHFILE, 231 | "debug", 232 | }; 233 | return pam_sm_authenticate((pam_handle_t *)5, 0, sizeof(cfg) / sizeof(char*), cfg); 234 | } 235 | 236 | static int test_authenticate5(void) { 237 | const char *cfg[] = { 238 | "id=1", 239 | "urllist=http://localhost:"YKVAL_PORT1"/wsapi/2/verify;http://localhost:"YKVAL_PORT2"/wsapi/2/verify", 240 | "authfile="AUTHFILE, 241 | "debug", 242 | }; 243 | return pam_sm_authenticate((pam_handle_t *)6, 0, sizeof(cfg) / sizeof(char*), cfg); 244 | } 245 | 246 | static int test_fail_authenticate1(void) { 247 | const char *cfg[] = { 248 | "id=1", 249 | "urllist=http://localhost:"YKVAL_PORT2"/wsapi/2/verify;http://localhost:"YKVAL_PORT1"/wsapi/2/verify", 250 | "authfile="AUTHFILE, 251 | "debug" 252 | }; 253 | return pam_sm_authenticate((pam_handle_t *)1, 0, sizeof(cfg) / sizeof(char*), cfg); 254 | } 255 | 256 | static int test_fail_authenticate2(void) { 257 | const char *cfg[] = { 258 | "id=1", 259 | "urllist=http://localhost:"YKVAL_PORT2"/wsapi/2/verify;http://localhost:"YKVAL_PORT1"/wsapi/2/verify", 260 | "authfile="AUTHFILE, 261 | "debug" 262 | }; 263 | return pam_sm_authenticate((pam_handle_t *)2, 0, sizeof(cfg) / sizeof(char*), cfg); 264 | } 265 | 266 | static int test_fail_authenticate3(void) { 267 | const char *cfg[] = { 268 | "id=1", 269 | "urllist=http://localhost:"YKVAL_PORT2"/wsapi/2/verify", 270 | "authfile="AUTHFILE, 271 | "debug" 272 | }; 273 | return pam_sm_authenticate((pam_handle_t *)3, 0, sizeof(cfg) / sizeof(char*), cfg); 274 | } 275 | 276 | static int test_firstpass_authenticate(void) { 277 | const char *cfg[] = { 278 | "id=1", 279 | "urllist=http://localhost:"YKVAL_PORT2"/wsapi/2/verify;http://localhost:"YKVAL_PORT1"/wsapi/2/verify", 280 | "authfile="AUTHFILE, 281 | "use_first_pass", 282 | "debug" 283 | }; 284 | return pam_sm_authenticate((pam_handle_t *)8, 0, sizeof(cfg) / sizeof(char*), cfg); 285 | } 286 | 287 | static int test_firstpass_fail(void) { 288 | const char *cfg[] = { 289 | "id=1", 290 | "urllist=http://localhost:"YKVAL_PORT2"/wsapi/2/verify;http://localhost:"YKVAL_PORT1"/wsapi/2/verify", 291 | "authfile="AUTHFILE, 292 | "use_first_pass", 293 | "debug" 294 | }; 295 | return pam_sm_authenticate((pam_handle_t *)9, 0, sizeof(cfg) / sizeof(char*), cfg); 296 | } 297 | 298 | static int test_firstpass_fail2(void) { 299 | const char *cfg[] = { 300 | "id=1", 301 | "urllist=http://localhost:"YKVAL_PORT2"/wsapi/2/verify;http://localhost:"YKVAL_PORT1"/wsapi/2/verify", 302 | "authfile="AUTHFILE, 303 | "use_first_pass", 304 | "debug" 305 | }; 306 | return pam_sm_authenticate((pam_handle_t *)10, 0, sizeof(cfg) / sizeof(char*), cfg); 307 | } 308 | 309 | static int test_authenticate_ldap1(void) { 310 | return pam_sm_authenticate((pam_handle_t *)0, 0, sizeof(ldap_cfg) / sizeof(char*), ldap_cfg); 311 | } 312 | 313 | static int test_authenticate_ldap_fail1(void) { 314 | return pam_sm_authenticate((pam_handle_t *)1, 0, sizeof(ldap_cfg) / sizeof(char*), ldap_cfg); 315 | } 316 | 317 | static int test_authenticate_ldap_fail2(void) { 318 | return pam_sm_authenticate((pam_handle_t *)2, 0, sizeof(ldap_cfg) / sizeof(char*), ldap_cfg); 319 | } 320 | 321 | static int test_authenticate_ldap2(void) { 322 | return pam_sm_authenticate((pam_handle_t *)4, 0, sizeof(ldap_cfg) / sizeof(char*), ldap_cfg); 323 | } 324 | 325 | static int test_authenticate_ldap3(void) { 326 | return pam_sm_authenticate((pam_handle_t *)4, 0, sizeof(ldap_cfg2) / sizeof(char*), ldap_cfg2); 327 | } 328 | 329 | static int test_authenticate_ldap4(void) { 330 | return pam_sm_authenticate((pam_handle_t *)5, 0, sizeof(ldap_cfg) / sizeof(char*), ldap_cfg); 331 | } 332 | 333 | static int test_authenticate_ldap5(void) { 334 | return pam_sm_authenticate((pam_handle_t *)6, 0, sizeof(ldap_cfg) / sizeof(char*), ldap_cfg); 335 | } 336 | 337 | static int test_authenticate_ldap6(void) { 338 | return pam_sm_authenticate((pam_handle_t *)7, 0, sizeof(ldap_cfg) / sizeof(char*), ldap_cfg); 339 | } 340 | 341 | static int test_authenticate_mysql1(void) { 342 | return pam_sm_authenticate((pam_handle_t *)0, 0, sizeof(mysql_cfg) / sizeof(char*), mysql_cfg); 343 | } 344 | 345 | static int test_fail_authenticate_mysql1(void) { 346 | return pam_sm_authenticate((pam_handle_t *)1, 0, sizeof(mysql_cfg) / sizeof(char*), mysql_cfg); 347 | } 348 | 349 | static int test_fail_authenticate_mysql2(void) { 350 | return pam_sm_authenticate((pam_handle_t *)5, 0, sizeof(mysql_cfg) / sizeof(char*), mysql_cfg); 351 | } 352 | 353 | static pid_t run_mock(const char *port, const char *type) { 354 | pid_t pid = fork(); 355 | if(pid == 0) { 356 | execlp(type, type, port, NULL); 357 | } 358 | return pid; 359 | } 360 | 361 | int main(void) { 362 | int ret = 0; 363 | pid_t child = run_mock(YKVAL_PORT1, YKVAL); 364 | pid_t child2 = run_mock(YKVAL_PORT2, YKVAL); 365 | #ifdef HAVE_LIBLDAP 366 | pid_t child3 = run_mock(LDAP_PORT, LDAP); 367 | #endif 368 | 369 | /* Give the "server" time to settle */ 370 | sleep(1); 371 | 372 | if(test_authenticate1() != PAM_SUCCESS) { 373 | ret = 1; 374 | goto out; 375 | } 376 | if(test_authenticate2() != PAM_SUCCESS) { 377 | ret = 2; 378 | goto out; 379 | } 380 | if(test_fail_authenticate1() != PAM_USER_UNKNOWN) { 381 | ret = 3; 382 | goto out; 383 | } 384 | if(test_fail_authenticate2() != PAM_AUTH_ERR) { 385 | ret = 4; 386 | goto out; 387 | } 388 | if(test_fail_authenticate3() != PAM_AUTH_ERR) { 389 | ret = 5; 390 | goto out; 391 | } 392 | if(test_authenticate3() != PAM_SUCCESS) { 393 | ret = 6; 394 | goto out; 395 | } 396 | if(test_authenticate4() != PAM_AUTH_ERR) { 397 | ret = 7; 398 | goto out; 399 | } 400 | if(test_authenticate5() != PAM_USER_UNKNOWN) { 401 | ret = 8; 402 | goto out; 403 | } 404 | if(test_firstpass_authenticate() != PAM_SUCCESS) { 405 | ret = 9; 406 | goto out; 407 | } 408 | if(test_firstpass_fail() != PAM_AUTH_ERR) { 409 | ret = 10; 410 | goto out; 411 | } 412 | if(test_firstpass_fail2() != PAM_USER_UNKNOWN) { 413 | ret = 11; 414 | goto out; 415 | } 416 | #ifdef HAVE_LIBLDAP 417 | if(test_authenticate_ldap1() != PAM_SUCCESS) { 418 | ret = 1001; 419 | goto out; 420 | } 421 | if(test_authenticate_ldap_fail1() != PAM_USER_UNKNOWN) { 422 | ret = 1002; 423 | goto out; 424 | } 425 | if(test_authenticate_ldap_fail2() != PAM_AUTH_ERR) { 426 | ret = 1003; 427 | goto out; 428 | } 429 | if(test_authenticate_ldap2() != PAM_SUCCESS) { 430 | ret = 1004; 431 | goto out; 432 | } 433 | if(test_authenticate_ldap3() != PAM_SUCCESS) { 434 | ret = 1005; 435 | goto out; 436 | } 437 | if(test_authenticate_ldap4() != PAM_AUTH_ERR) { 438 | ret = 1006; 439 | goto out; 440 | } 441 | if(test_authenticate_ldap5() != PAM_USER_UNKNOWN) { 442 | ret = 1007; 443 | goto out; 444 | } 445 | if(test_authenticate_ldap6() != PAM_USER_UNKNOWN) { 446 | ret = 1008; 447 | goto out; 448 | } 449 | #endif 450 | #if defined(RUN_MYSQL_TESTS) && defined(HAVE_MYSQL) 451 | if(test_authenticate_mysql1() != PAM_SUCCESS) { 452 | ret = 2001; 453 | goto out; 454 | } 455 | if(test_fail_authenticate_mysql1() != PAM_USER_UNKNOWN) { 456 | ret = 2002; 457 | goto out; 458 | } 459 | if(test_fail_authenticate_mysql2() != PAM_AUTH_ERR) { 460 | ret = 2003; 461 | goto out; 462 | } 463 | #endif 464 | 465 | out: 466 | kill(child, 9); 467 | kill(child2, 9); 468 | #ifdef HAVE_LIBLDAP 469 | kill(child3, 9); 470 | printf("killed %d, %d and %d\n", child, child2, child3); 471 | #else 472 | printf("killed %d and %d\n", child, child2); 473 | #endif 474 | if(ret != 0) { 475 | fprintf(stderr, "test %d failed!\n", ret); 476 | } 477 | return ret; 478 | } 479 | -------------------------------------------------------------------------------- /tests/util_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Yubico AB 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 12 | * * Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following 14 | * disclaimer in the documentation and/or other materials provided 15 | * with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include 41 | 42 | #include "util.h" 43 | 44 | static void test_get_user_cfgfile_path(void) { 45 | char *file; 46 | struct passwd user; 47 | int ret; 48 | user.pw_name = "root"; 49 | user.pw_dir = "/root"; 50 | ret = get_user_cfgfile_path("/foo/bar", "test", &user, &file); 51 | assert(ret == 1); 52 | assert(strcmp(file, "/foo/bar/test") == 0); 53 | free(file); 54 | ret = get_user_cfgfile_path(NULL, "test", &user, &file); 55 | assert(ret == 1); 56 | assert(strcmp(file, "/root/.yubico/test") == 0); 57 | free(file); 58 | } 59 | 60 | static void test_check_user_token(void) { 61 | char file[] = "/tmp/pamtest.XXXXXX"; 62 | int fd = mkstemp(file); 63 | FILE *handle; 64 | int ret; 65 | 66 | assert(fd != -1); 67 | handle = fdopen(fd, "w"); 68 | fprintf(handle, "# This is a comment containing foobar:foobar\n"); 69 | fprintf(handle, "foobar:hhhvhvhdhbid:hnhbhnhbhnhb:\n"); 70 | fprintf(handle, "# This is a comment in the middle\n"); 71 | fprintf(handle, "kaka:hdhrhbhjhvhu:hihbhdhrhbhj\n"); 72 | fprintf(handle, "# foo2 is a user showing up twice in the file\n"); 73 | fprintf(handle, "foo2:vvvvvvvvvvvv\n"); 74 | fprintf(handle, "bar:hnhbhnhbhnhb\n"); 75 | fprintf(handle, "foo2:cccccccccccc\n"); 76 | fclose(handle); 77 | 78 | ret = check_user_token(file, "foobar", "hhhvhvhdhbid", 1, stdout); 79 | assert(ret == AUTH_FOUND); 80 | ret = check_user_token(file, "foobar", "hnhbhnhbhnhb", 1, stdout); 81 | assert(ret == AUTH_FOUND); 82 | ret = check_user_token(file, "foobar", "hnhbhnhbhnhc", 1, stdout); 83 | assert(ret == AUTH_NOT_FOUND); 84 | ret = check_user_token(file, "kaka", "hihbhdhrhbhj", 1, stdout); 85 | assert(ret == AUTH_FOUND); 86 | ret = check_user_token(file, "bar", "hnhbhnhbhnhb", 1, stdout); 87 | assert(ret == AUTH_FOUND); 88 | ret = check_user_token(file, "foo", "hdhrhbhjhvhu", 1, stdout); 89 | assert(ret == AUTH_NO_TOKENS); 90 | ret = check_user_token(file, "foo2", "cccccccccccc", 1, stdout); 91 | assert(ret == AUTH_FOUND); 92 | ret = check_user_token(file, "foo2", "vvvvvvvvvvvv", 1, stdout); 93 | assert(ret == AUTH_FOUND); 94 | ret = check_user_token(file, "foo2", "vvvvvvvvvvcc", 1, stdout); 95 | assert(ret == AUTH_NOT_FOUND); 96 | ret = check_user_token(file, "foo2", "", 1, stdout); 97 | assert(ret == AUTH_NOT_FOUND); 98 | ret = check_user_token(file, "foo", "", 1, stdout); 99 | assert(ret == AUTH_NO_TOKENS); 100 | remove(file); 101 | } 102 | 103 | #if HAVE_CR 104 | 105 | #define CHALLENGE1 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 106 | #define RESPONSE1 "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" 107 | #define SALT1 "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" 108 | #define CHALLENGE2 "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" 109 | #define RESPONSE2 "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" 110 | 111 | static void test_load_chalresp_state(void) { 112 | int ret; 113 | FILE *file = tmpfile(); 114 | CR_STATE state; 115 | 116 | memset(&state, 0, sizeof(state)); 117 | fprintf(file, "v2:%s:%s:%s:%d:%d\n", CHALLENGE1, RESPONSE1, SALT1, 1000, 2); 118 | rewind(file); 119 | ret = load_chalresp_state(file, &state, true, stdout); 120 | assert(ret == 1); 121 | assert(state.iterations == 1000); 122 | assert(state.slot == 2); 123 | assert(state.challenge_len == CR_CHALLENGE_SIZE); 124 | assert(state.response_len == CR_RESPONSE_SIZE); 125 | assert(state.salt_len == CR_SALT_SIZE); 126 | rewind(file); 127 | 128 | memset(&state, 0, sizeof(state)); 129 | fprintf(file, "v1:%s:%s:%d\n", CHALLENGE2, RESPONSE2, 1); 130 | rewind(file); 131 | ret = load_chalresp_state(file, &state, true, stdout); 132 | assert(ret == 1); 133 | assert(state.iterations == CR_DEFAULT_ITERATIONS); 134 | assert(state.slot == 1); 135 | assert(state.challenge_len == CR_CHALLENGE_SIZE); 136 | assert(state.response_len == CR_RESPONSE_SIZE); 137 | assert(state.salt_len == 0); 138 | rewind(file); 139 | 140 | /* slot 3 should fail.. */ 141 | fprintf(file, "v2:%s:%s:%s:%d:%d\n", CHALLENGE1, RESPONSE1, SALT1, 1000, 3); 142 | rewind(file); 143 | ret = load_chalresp_state(file, &state, true, stdout); 144 | assert(ret == 0); 145 | fclose(file); 146 | } 147 | 148 | static void test_check_user_challenge_file(void) { 149 | int ret; 150 | char * tmpdir_path; 151 | char * buf; 152 | FILE * file; 153 | struct passwd user; 154 | 155 | buf = malloc(256); 156 | 157 | #define create_tmpdir_dir(path) \ 158 | strcpy(buf, tmpdir_path); \ 159 | strcat(buf, "/"); \ 160 | strcat(buf, path); \ 161 | mkdir(buf, 0755); 162 | 163 | #define remove_tmpdir_dir(path) \ 164 | strcpy(buf, tmpdir_path); \ 165 | strcat(buf, "/"); \ 166 | strcat(buf, path); \ 167 | rmdir(buf); 168 | 169 | #define create_tmpdir_file(path) \ 170 | strcpy(buf, tmpdir_path); \ 171 | strcat(buf, "/"); \ 172 | strcat(buf, path); \ 173 | file = fopen(buf, "w"); \ 174 | fclose(file); 175 | 176 | #define remove_tmpdir_file(path) \ 177 | strcpy(buf, tmpdir_path); \ 178 | strcat(buf, "/"); \ 179 | strcat(buf, path); \ 180 | unlink(buf); 181 | 182 | /* create temporary directory */ 183 | char template[] = "/tmp/pamtest.XXXXXX"; 184 | tmpdir_path = mkdtemp(template); 185 | assert(tmpdir_path != NULL); 186 | 187 | /* set user data */ 188 | user.pw_name = "tester"; 189 | user.pw_dir = tmpdir_path; 190 | 191 | /* execute tests */ 192 | /* no asserts here as we have directory to remove */ 193 | 194 | int case_001_empty_chalresp_dir; 195 | case_001_empty_chalresp_dir = check_user_challenge_file(tmpdir_path, &user, stdout); 196 | 197 | int case_002_one_challenge_file; 198 | create_tmpdir_file("tester"); 199 | case_002_one_challenge_file = check_user_challenge_file(tmpdir_path, &user, stdout); 200 | remove_tmpdir_file("tester"); 201 | 202 | int case_003_multiple_challenge_files; 203 | create_tmpdir_file("tester-001"); 204 | create_tmpdir_file("tester-002"); 205 | case_003_multiple_challenge_files = check_user_challenge_file(tmpdir_path, &user, stdout); 206 | remove_tmpdir_file("tester-002"); 207 | remove_tmpdir_file("tester-001"); 208 | 209 | int case_004_other_users_files; 210 | create_tmpdir_file("tester1"); 211 | create_tmpdir_file("tester1-001"); 212 | case_004_other_users_files = check_user_challenge_file(tmpdir_path, &user, stdout); 213 | remove_tmpdir_file("tester1-001"); 214 | remove_tmpdir_file("tester1"); 215 | 216 | int case_005_no_chalresp_no_yubico; 217 | case_005_no_chalresp_no_yubico = check_user_challenge_file(NULL, &user, stdout); 218 | 219 | int case_006_no_chalresp_empty_yubico; 220 | create_tmpdir_dir(".yubico"); 221 | case_006_no_chalresp_empty_yubico = check_user_challenge_file(NULL, &user, stdout); 222 | remove_tmpdir_dir(".yubico"); 223 | 224 | int case_007_no_chalresp_one_challenge_file; 225 | create_tmpdir_dir(".yubico"); 226 | create_tmpdir_file(".yubico/challenge"); 227 | case_007_no_chalresp_one_challenge_file = check_user_challenge_file(NULL, &user, stdout); 228 | remove_tmpdir_file(".yubico/challenge"); 229 | remove_tmpdir_dir(".yubico"); 230 | 231 | int case_008_no_chalresp_multiple_challenge_files; 232 | create_tmpdir_dir(".yubico"); 233 | create_tmpdir_file(".yubico/challenge-001"); 234 | create_tmpdir_file(".yubico/challenge-002"); 235 | case_008_no_chalresp_multiple_challenge_files = check_user_challenge_file(NULL, &user, stdout); 236 | remove_tmpdir_file(".yubico/challenge-002"); 237 | remove_tmpdir_file(".yubico/challenge-001"); 238 | remove_tmpdir_dir(".yubico"); 239 | 240 | /* remove temporary directory */ 241 | ret = rmdir(tmpdir_path); 242 | assert(ret == 0); 243 | free(buf); 244 | 245 | /* check test results */ 246 | assert(case_001_empty_chalresp_dir == AUTH_NOT_FOUND); 247 | assert(case_002_one_challenge_file == AUTH_FOUND); 248 | assert(case_003_multiple_challenge_files == AUTH_FOUND); 249 | assert(case_004_other_users_files == AUTH_NOT_FOUND); 250 | assert(case_005_no_chalresp_no_yubico == AUTH_NOT_FOUND); 251 | assert(case_006_no_chalresp_empty_yubico == AUTH_NOT_FOUND); 252 | assert(case_007_no_chalresp_one_challenge_file == AUTH_FOUND); 253 | assert(case_008_no_chalresp_multiple_challenge_files == AUTH_FOUND); 254 | 255 | #undef create_tmpdir_dir 256 | #undef remove_tmpdir_dir 257 | #undef create_tmpdir_file 258 | #undef remove_tmpdir_file 259 | } 260 | 261 | #endif /* HAVE_CR */ 262 | 263 | static void test_filter_printf(void) { 264 | assert(filter_result_len("meno %u", "doof", NULL) == 10); 265 | assert(filter_result_len("meno %u %u", "doof", NULL) == 15); 266 | assert(filter_result_len("%u meno %u", "doof", NULL) == 15); 267 | assert(filter_result_len("%u me %u no %u", "doof", NULL) == 21); 268 | assert(filter_result_len("meno %w %%u", "doof", NULL) == 14); 269 | assert(filter_result_len("meno %w %%u meno", "doof", NULL) == 19); 270 | assert(filter_result_len("meno ", "doof", NULL) == 6); 271 | 272 | assert(!strcmp(filter_printf("meno %u", "doof"), "meno doof")); 273 | assert(!strcmp(filter_printf("meno %u %u", "doof"), "meno doof doof")); 274 | assert(!strcmp(filter_printf("%u meno %u", "doof"), "doof meno doof")); 275 | assert(!strcmp(filter_printf("%u me %u no %u", "doof"), "doof me doof no doof")); 276 | assert(!strcmp(filter_printf("meno %w %%u", "doof"), "meno %w %doof")); 277 | assert(!strcmp(filter_printf("meno %w %%u meno", "doof"), "meno %w %doof meno")); 278 | assert(!strcmp(filter_printf("meno ", "doof"), "meno ")); 279 | printf("test_filter_printf OK\n"); 280 | } 281 | 282 | static void test_rfc4515_replace(void) { 283 | assert(rfc4515_length("", NULL) == 1); 284 | assert(rfc4515_length(" ", NULL) == 2); 285 | assert(rfc4515_length("test1234567890_.", NULL) == 17); 286 | assert(rfc4515_length("\\test\\test\\", NULL) == 18); 287 | assert(rfc4515_length("*test*test*", NULL) == 18); 288 | assert(rfc4515_length("(test(test(", NULL) == 18); 289 | assert(rfc4515_length(")test)test)", NULL) == 18); 290 | assert(rfc4515_length("\\\\**(())", NULL) == 25); 291 | 292 | assert(!strcmp(rfc4515_replace(""), "")); 293 | assert(!strcmp(rfc4515_replace(" "), " ")); 294 | assert(!strcmp(rfc4515_replace("test1234567890_."), "test1234567890_.")); 295 | assert(!strcmp(rfc4515_replace("\\test\\test\\"), "\\5Ctest\\5Ctest\\5C")); 296 | assert(!strcmp(rfc4515_replace("*test*test*"), "\\2Atest\\2Atest\\2A")); 297 | assert(!strcmp(rfc4515_replace("(test(test("), "\\28test\\28test\\28")); 298 | assert(!strcmp(rfc4515_replace(")test)test)"), "\\29test\\29test\\29")); 299 | assert(!strcmp(rfc4515_replace("\\\\**(())"), "\\5C\\5C\\2A\\2A\\28\\28\\29\\29")); 300 | printf("test_rfc4515_replace OK\n"); 301 | } 302 | int main (void) { 303 | test_filter_printf(); 304 | test_rfc4515_replace(); 305 | test_get_user_cfgfile_path(); 306 | test_check_user_token(); 307 | #if HAVE_CR 308 | test_load_chalresp_state(); 309 | test_check_user_challenge_file(); 310 | #endif /* HAVE_CR */ 311 | return 0; 312 | } 313 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2014 Yubico AB 3 | * Copyright (c) 2011 Tollef Fog Heen 4 | * All rights reserved. 5 | * 6 | * Author : Fredrik Thulin 7 | * Author : Tollef Fog Heen 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above 17 | * copyright notice, this list of conditions and the following 18 | * disclaimer in the documentation and/or other materials provided 19 | * with the distribution. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | */ 33 | 34 | #ifndef __PAM_YUBICO_UTIL_H_INCLUDED__ 35 | #define __PAM_YUBICO_UTIL_H_INCLUDED__ 36 | 37 | #include 38 | #include 39 | #include 40 | 41 | #define D(file, x...) do { \ 42 | fprintf (file, "debug: %s:%d (%s): ", __FILE__, __LINE__, __FUNCTION__); \ 43 | fprintf (file, x); \ 44 | fprintf (file, "\n"); \ 45 | } while (0) 46 | 47 | /* Return values for authorize_user_token and authorize_user_token_ldap */ 48 | #define AUTH_NO_TOKENS -2 /* The user has no associated tokens */ 49 | #define AUTH_ERROR 0 /* Internal error when looking up associated tokens */ 50 | #define AUTH_FOUND 1 /* The requested token is associated to the user */ 51 | #define AUTH_NOT_FOUND -1 /* The requested token is not associated to the user */ 52 | 53 | int get_user_cfgfile_path(const char *common_path, const char *filename, const struct passwd *user, char **fn); 54 | #ifdef HAVE_MYSQL 55 | int check_user_token_mysql(const char *mysql_server, int mysql_port, const char *mysql_user, 56 | const char *mysql_password, const char *mysql_database, const char *username, const char *otp_id, int verbose, 57 | FILE *debug_file); 58 | #endif 59 | int check_user_token(const char *authfile, const char *username, const char *otp_id, int verbose, FILE *debug_file); 60 | 61 | #if HAVE_CR 62 | #include 63 | 64 | /* Challenges can be 0..63 or 64 bytes long, depending on YubiKey configuration. 65 | * We settle for 63 bytes to have something that works with all configurations. 66 | */ 67 | #define CR_CHALLENGE_SIZE 63 68 | #define CR_RESPONSE_SIZE 20 69 | #define CR_SALT_SIZE 32 70 | 71 | #define CR_DEFAULT_ITERATIONS 10000 72 | 73 | struct chalresp_state { 74 | char challenge[CR_CHALLENGE_SIZE]; 75 | uint8_t challenge_len; 76 | char response[CR_RESPONSE_SIZE]; 77 | uint8_t response_len; 78 | char salt[CR_SALT_SIZE]; 79 | uint8_t salt_len; 80 | uint8_t slot; 81 | uint32_t iterations; 82 | }; 83 | 84 | typedef struct chalresp_state CR_STATE; 85 | 86 | int generate_random(void *buf, int len); 87 | 88 | int check_user_challenge_file(const char *chalresp_path, const struct passwd *user, FILE *debug_file); 89 | int get_user_challenge_file(YK_KEY *yk, const char *chalresp_path, const struct passwd *user, char **fn, FILE *debug_file); 90 | 91 | int load_chalresp_state(FILE *f, CR_STATE *state, bool verbose, FILE *debug_file); 92 | int write_chalresp_state(FILE *f, CR_STATE *state); 93 | 94 | int init_yubikey(YK_KEY **yk); 95 | int check_firmware_version(YK_KEY *yk, bool verbose, bool quiet, FILE *debug_file); 96 | int challenge_response(YK_KEY *yk, int slot, 97 | char *challenge, unsigned int len, 98 | bool hmac, bool may_block, bool verbose, 99 | char *response, unsigned int res_size, unsigned int *res_len); 100 | 101 | #endif /* HAVE_CR */ 102 | 103 | size_t filter_result_len(const char *filter, const char *user, char *output); 104 | char *filter_printf(const char *filter, const char *user); 105 | 106 | size_t rfc4515_length ( const char* in, char* out ); 107 | char* rfc4515_replace ( const char* in ); 108 | 109 | #endif /* __PAM_YUBICO_UTIL_H_INCLUDED__ */ 110 | -------------------------------------------------------------------------------- /ykbzero.h: -------------------------------------------------------------------------------- 1 | /* -*- mode:C; c-file-style: "bsd" -*- */ 2 | /* 3 | * Copyright (c) 2008-2019 Yubico AB 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are 8 | * met: 9 | * 10 | * * Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above 14 | * copyright notice, this list of conditions and the following 15 | * disclaimer in the documentation and/or other materials provided 16 | * with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef __YKBZERO_H_INCLUDED__ 32 | #define __YKBZERO_H_INCLUDED__ 33 | 34 | #ifdef _WIN32 35 | #include 36 | #else 37 | #include 38 | #endif 39 | 40 | #ifdef _WIN32 41 | #define insecure_memzero(buf, len) SecureZeroMemory(buf, len) 42 | #elif HAVE_MEMSET_S 43 | #define insecure_memzero(buf, len) memset_s(buf, len, 0, len) 44 | #elif HAVE_EXPLICIT_BZERO 45 | #define insecure_memzero(buf, len) explicit_bzero(buf, len) 46 | #elif HAVE_EXPLICIT_MEMSET 47 | #define insecure_memzero(buf, len) explicit_memset(buf, 0, len) 48 | #elif HAVE_INLINE_ASM 49 | #define insecure_memzero(buf, len) do { \ 50 | memset(buf, 0, len); \ 51 | __asm__ __volatile__ ("" : : "r"(buf) : "memory"); \ 52 | } while (0) 53 | #else 54 | #define insecure_memzero(buf, len) do { \ 55 | volatile unsigned char *volatile __buf_ = \ 56 | (volatile unsigned char *volatile)buf; \ 57 | size_t __i_ = 0; \ 58 | while (__i_ < len) __buf_[__i_++] = 0; \ 59 | } while (0) 60 | #endif 61 | 62 | #endif /* __YKBZERO_H_INCLUDED__ */ 63 | -------------------------------------------------------------------------------- /ykpamcfg.1.txt: -------------------------------------------------------------------------------- 1 | YKPAMCFG(1) 2 | ========= 3 | :doctype: manpage 4 | :man source: yubico-pam 5 | :man manual: Yubico PAM Module Manual 6 | 7 | == NAME 8 | ykpamcfg - Manage user settings for the Yubico PAM module 9 | 10 | == SYNOPSIS 11 | *ykpamcfg* [-1 | -2] [-A] [-p] [-i] [-v] [-V] [-h] 12 | 13 | == OPTIONS 14 | *-1*:: 15 | Use slot 1. This is the default. 16 | 17 | *-2*:: 18 | Use slot 2. 19 | 20 | *-A* _action_:: 21 | Choose action to perform. See ACTIONS below. 22 | 23 | *-p* _path_:: 24 | Specify output file, default is `~/.yubico/challenge`. 25 | 26 | *-i* _iterations_:: 27 | Number of iterations to use for PBKDF2 of expected response. 28 | 29 | *-v*:: 30 | Enable verbose mode. 31 | 32 | *-V*:: 33 | Display version and exit. 34 | 35 | *-h*:: 36 | Display help and exit. 37 | 38 | == ACTIONS 39 | === add_hmac_chalresp 40 | The PAM module can utilize the HMAC-SHA1 Challenge-response (C/R) mode found in YubiKeys starting with version 2.2 for *offline authentication*. This action creates the initial state information with the C/R to be issued at the next logon. 41 | 42 | The utility currently outputs the state information to a file in the current user's home directory (`~/.yubico/challenge-123456` for a YubiKey with serial number API readout enabled, and `~/.yubico/challenge` for one without). 43 | 44 | The PAM module supports a system-wide directory for these state files (in case the user's home directories are encrypted), but in a system-wide directory, the 'challenge' part should be replaced with the username. Example: /var/yubico/challenges/alice-123456 45 | 46 | To use the system-wide mode, you currently have to move the generated state files manually and configure the PAM module accordingly. 47 | 48 | == EXAMPLES 49 | 50 | First, program a YubiKey for challenge-response on slot 2: 51 | 52 | $ ykpersonalize -2 -ochal-resp -ochal-hmac -ohmac-lt64 -oserial-api-visible 53 | ... 54 | Commit? (y/n) [n]: y 55 | 56 | Now, set the current user to require this YubiKey for logon: 57 | 58 | $ ykpamcfg -2 -v 59 | ... 60 | Stored initial challenge and expected response in '/home/alice/.yubico/challenge-123456'. 61 | 62 | Then, configure authentication with PAM for example like this (_make a backup first_): 63 | 64 | _/etc/pam.d/common-auth_ (from Ubuntu 10.10): 65 | 66 | auth required pam_unix.so nullok_secure try_first_pass 67 | auth [success=1 new_authtok_reqd=ok ignore=ignore default=die] pam_yubico.so mode=challenge-response 68 | auth requisite pam_deny.so 69 | auth required pam_permit.so 70 | auth optional pam_ecryptfs.so unwrap 71 | 72 | == BUGS 73 | Report ykpamcfg bugs in the issue tracker: https://github.com/Yubico/yubico-pam/issues 74 | 75 | == SEE ALSO 76 | *pam_yubico*(8) 77 | 78 | The yubico-pam home page: https://developers.yubico.com/yubico-pam/ 79 | 80 | YubiKeys can be obtained from Yubico: http://www.yubico.com/ 81 | -------------------------------------------------------------------------------- /ykpamcfg.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2014 Yubico AB 3 | * All rights reserved. 4 | * 5 | * Author : Fredrik Thulin 6 | * 7 | * Based on ykchalresp.c from yubikey-personalization. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above 17 | * copyright notice, this list of conditions and the following 18 | * disclaimer in the documentation and/or other materials provided 19 | * with the distribution. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | */ 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #include 45 | 46 | #include "util.h" 47 | 48 | #define ACTION_ADD_HMAC_CHALRESP "add_hmac_chalresp" 49 | #define ACTION_MAX_LEN 1024 50 | 51 | const char *usage = 52 | "Usage: ykpamcfg [options]\n" 53 | "\n" 54 | "Options :\n" 55 | "\n" 56 | "\t-1 Send challenge to slot 1. This is the default.\n" 57 | "\t-2 Send challenge to slot 2.\n" 58 | "\t-A action What to do.\n" 59 | "\t-p path Specify an output path for the challenge file.\n" 60 | "\t-i iters Number of iterations to use for PBKDF2 (defaults to 10000)\n" 61 | "\n" 62 | "\t-v Increase verbosity\n" 63 | "\t-V Show version and exit\n" 64 | "\t-h Show help (this text) and exit\n" 65 | "\n" 66 | "Actions :\n" 67 | "\n" 68 | "\t" ACTION_ADD_HMAC_CHALRESP "\tAdds a challenge-response state file for a connected YubiKey (default)\n" 69 | "\n" 70 | "\n" 71 | ; 72 | const char *optstring = "12A:p:i:vVh"; 73 | 74 | static void 75 | report_yk_error(void) 76 | { 77 | if (ykp_errno) 78 | fprintf(stderr, "YubiKey personalization error: %s\n", 79 | ykp_strerror(ykp_errno)); 80 | if (yk_errno) { 81 | if (yk_errno == YK_EUSBERR) { 82 | fprintf(stderr, "USB error: %s\n", 83 | yk_usb_strerror()); 84 | } else { 85 | fprintf(stderr, "YubiKey core error: %s\n", 86 | yk_strerror(yk_errno)); 87 | } 88 | } 89 | } 90 | 91 | static int 92 | parse_args(int argc, char **argv, 93 | int *slot, bool *verbose, 94 | char **action, char **output_dir, 95 | unsigned int *iterations) 96 | { 97 | int c; 98 | int i; 99 | 100 | while((c = getopt(argc, argv, optstring)) != -1) { 101 | switch (c) { 102 | case '1': 103 | *slot = 1; 104 | break; 105 | case '2': 106 | *slot = 2; 107 | break; 108 | case 'A': 109 | i = snprintf(*action, ACTION_MAX_LEN, "%s", optarg); 110 | if (i < 0 || i >= ACTION_MAX_LEN) { 111 | fprintf(stderr, "action too long: %s\n", optarg); 112 | exit(1); 113 | } 114 | break; 115 | case 'p': 116 | *output_dir = optarg; 117 | break; 118 | case 'i': 119 | { 120 | char *endptr; 121 | *iterations = strtoul(optarg, &endptr, 10); 122 | if(*endptr != '\0') { 123 | fprintf(stderr, "iterations must be numeric, %s isn't.\n", optarg); 124 | exit(1); 125 | } 126 | } 127 | break; 128 | case 'v': 129 | *verbose = true; 130 | break; 131 | case 'V': 132 | printf("%s\n", VERSION); 133 | exit(0); 134 | case 'h': 135 | default: 136 | fputs(usage, stderr); 137 | exit(0); 138 | } 139 | } 140 | 141 | return 1; 142 | } 143 | 144 | static int 145 | do_add_hmac_chalresp(YK_KEY *yk, uint8_t slot, bool verbose, char *output_dir, unsigned int iterations, int *exit_code) 146 | { 147 | char buf[CR_RESPONSE_SIZE + 16]; 148 | CR_STATE state; 149 | int ret = 0; 150 | unsigned int response_len; 151 | char *fn; 152 | struct passwd *p; 153 | int fd; 154 | FILE *f = NULL; 155 | struct stat st; 156 | 157 | state.iterations = iterations; 158 | state.slot = slot; 159 | *exit_code = 1; 160 | 161 | p = getpwuid (getuid ()); 162 | 163 | if (! p) { 164 | fprintf (stderr, "Who am I???"); 165 | goto out; 166 | } 167 | 168 | /* 169 | * Create default output directory for the user 170 | */ 171 | 172 | if (!output_dir){ 173 | char fullpath[PATH_MAX]; 174 | int i = snprintf(fullpath, PATH_MAX, "%s/.yubico", p->pw_dir); 175 | 176 | if (i < 0 || i >= PATH_MAX) { 177 | fprintf(stderr, "Failed to construct fullpath: %s\n", p->pw_dir); 178 | goto out; 179 | } 180 | 181 | //check if directory exists 182 | if (stat(fullpath,&st)!=0 ){ 183 | if(mkdir(fullpath, S_IRWXU)==-1){ 184 | fprintf(stderr, "Failed to create directory '%s': %s\n", 185 | fullpath, strerror(errno)); 186 | } 187 | if(verbose){ 188 | printf("Directory %s created successfully.\n", fullpath); 189 | } 190 | } 191 | else{ 192 | if(!S_ISDIR(st.st_mode)){ 193 | fprintf(stderr, "Destination %s already exists and is not a directory.\n", 194 | fullpath); 195 | goto out; 196 | } 197 | } 198 | } 199 | 200 | if (! get_user_challenge_file(yk, output_dir, p, &fn, stdout)) { 201 | fprintf (stderr, "Failed to get chalresp state filename\n"); 202 | goto out; 203 | } 204 | 205 | if (stat(fn, &st) == 0) { 206 | fprintf(stderr, "File %s already exists, refusing to overwrite.\n", fn); 207 | goto out; 208 | } 209 | 210 | if (generate_random(state.challenge, CR_CHALLENGE_SIZE)) { 211 | fprintf (stderr, "Failed to get %i bytes of random data\n", CR_CHALLENGE_SIZE); 212 | goto out; 213 | } 214 | state.challenge_len = CR_CHALLENGE_SIZE; 215 | 216 | if (! challenge_response(yk, state.slot, state.challenge, CR_CHALLENGE_SIZE, 217 | true, true, verbose, 218 | buf, sizeof(buf), &response_len)) 219 | goto out; 220 | 221 | /* Make sure we get different responses for different challenges 222 | There is a firmware bug in YubiKey 2.2 that makes it issue same 223 | response for all challenges unless HMAC_LT64 is set. */ 224 | { 225 | char buf2[CR_RESPONSE_SIZE + 16]; 226 | char challenge[CR_CHALLENGE_SIZE]; 227 | 228 | if (generate_random(challenge, CR_CHALLENGE_SIZE)) { 229 | fprintf (stderr, "Failed to get %i bytes of random data\n", CR_CHALLENGE_SIZE); 230 | goto out; 231 | } 232 | if (! challenge_response(yk, state.slot, challenge, CR_CHALLENGE_SIZE, 233 | true, true, verbose, 234 | buf2, sizeof(buf2), &response_len)) 235 | goto out; 236 | 237 | if (memcmp(buf, buf2, response_len) == 0) { 238 | fprintf (stderr, "Failed: YubiKey is outputting the same response for different challenges." 239 | "Make sure you configure the key with the option HMAC_LT64.\n"); 240 | goto out; 241 | } 242 | } 243 | 244 | if (response_len > sizeof (state.response)) { 245 | fprintf (stderr, "Got too long response ??? (%u/%lu)", response_len, (unsigned long) sizeof(state.response)); 246 | goto out; 247 | } 248 | memcpy (state.response, buf, response_len); 249 | state.response_len = response_len; 250 | 251 | umask(077); 252 | 253 | fd = open (fn, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW | O_NOCTTY, S_IRUSR | S_IWUSR); 254 | if (fd < 0) { 255 | fprintf (stderr, "Failed to open '%s' for writing: %s\n", fn, strerror (errno)); 256 | goto out; 257 | } 258 | f = fdopen (fd, "w"); 259 | if (! f) { 260 | fprintf (stderr, "fdopen: %s\n", strerror (errno)); 261 | close(fd); 262 | goto out; 263 | } 264 | 265 | if (! write_chalresp_state (f, &state)) 266 | goto out; 267 | 268 | printf ("Stored initial challenge and expected response in '%s'.\n", fn); 269 | 270 | *exit_code = 0; 271 | ret = 1; 272 | 273 | out: 274 | if (f) 275 | fclose (f); 276 | 277 | return ret; 278 | } 279 | 280 | int 281 | main(int argc, char **argv) 282 | { 283 | YK_KEY *yk = NULL; 284 | bool error = true; 285 | int exit_code = 0; 286 | 287 | /* Options */ 288 | bool verbose = false; 289 | char action[ACTION_MAX_LEN]; 290 | char *ptr = action; 291 | char *output_dir = NULL; 292 | int slot = 1; 293 | unsigned int iterations = CR_DEFAULT_ITERATIONS; 294 | 295 | ykp_errno = 0; 296 | yk_errno = 0; 297 | 298 | strncpy(action, ACTION_ADD_HMAC_CHALRESP, ACTION_MAX_LEN); 299 | 300 | if (! parse_args(argc, argv, 301 | &slot, &verbose, 302 | &ptr, &output_dir, 303 | &iterations)) 304 | goto err; 305 | 306 | exit_code = 1; 307 | 308 | if (! strncmp(action, ACTION_ADD_HMAC_CHALRESP, ACTION_MAX_LEN)) { 309 | /* 310 | * Set up challenge-response login authentication 311 | */ 312 | if (! init_yubikey (&yk)) 313 | goto err; 314 | 315 | if (! check_firmware_version(yk, verbose, false, stdout)) 316 | goto err; 317 | 318 | if (! do_add_hmac_chalresp (yk, slot, verbose, output_dir, iterations, &exit_code)) 319 | goto err; 320 | } else { 321 | fprintf (stderr, "Unknown action '%s'\n", action); 322 | goto err; 323 | } 324 | 325 | exit_code = 0; 326 | error = false; 327 | 328 | err: 329 | if (error || exit_code != 0) { 330 | report_yk_error (); 331 | } 332 | 333 | if (yk && !yk_close_key (yk)) { 334 | report_yk_error (); 335 | exit_code = 2; 336 | } 337 | 338 | if (!yk_release ()) { 339 | report_yk_error (); 340 | exit_code = 2; 341 | } 342 | 343 | exit (exit_code); 344 | } 345 | -------------------------------------------------------------------------------- /yubikey_mapping.sql: -------------------------------------------------------------------------------- 1 | # 2 | # Table structure for table equiv of yubikey_mapping 3 | # 4 | 5 | CREATE TABLE IF NOT EXISTS `otp`.`yubikey_mappings` ( 6 | `otp_id` VARCHAR(12) NOT NULL , 7 | `username` VARCHAR(64) NOT NULL , 8 | PRIMARY KEY (`otp_id`(12)) 9 | ); 10 | --------------------------------------------------------------------------------