Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
security
google-authenticator-libpam
google-authenticator-libpam-1.10.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File google-authenticator-libpam-1.10.obscpio of Package google-authenticator-libpam
07070100000000000041ED0000000000000000000000026627DD8100000000000000000000000000000000000000000000002900000000google-authenticator-libpam-1.10/.github07070100000001000041ED0000000000000000000000026627DD8100000000000000000000000000000000000000000000003800000000google-authenticator-libpam-1.10/.github/ISSUE_TEMPLATE07070100000002000081A40000000000000000000000016627DD81000002DF000000000000000000000000000000000000004600000000google-authenticator-libpam-1.10/.github/ISSUE_TEMPLATE/bug_report.md--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- ### Describe the bug A clear and concise description of what the bug is. ### To Reproduce Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error ### Expected behavior A clear and concise description of what you expected to happen. ### Config. E.g. `/etc/ssh/sshd_config` ``` Paste the config here ``` ### Logs: `/var/log/auth.log` or equivalent ``` Paste the relevant log lines here ``` ### Environment - OS: [e.g. Ubuntu] - Version [e.g. 22.04] - Do you use selinux? (check with e.g. `sestatus`): ___ ### Additional context Add any other context about the problem here. 07070100000003000081A40000000000000000000000016627DD81000002AE000000000000000000000000000000000000004E00000000google-authenticator-libpam-1.10/.github/ISSUE_TEMPLATE/configuration-help.md--- name: Configuration help about: Describe this issue template's purpose here. title: '' labels: '' assignees: '' --- ### System information Operating system (e.g. Ubuntu 22.04): ____ Do you use selinux? (check with e.g. `sestatus`): ___ ### Steps to reproduce 1. … ### What I expected would happen FILL IN ### What actually happened FILL IN ### PAM config Paste the relevant parts of your PAM config ``` paste here ``` ### If SSH: SSH config Paste the relevant parts of `/etc/ssh/sshd_config` or equivalent. ``` paste here ``` ### If not SSH: That program's config, and logs ``` paste here ``` ### Contents of `/var/log/auth.log` or equivalent ``` paste here ``` 07070100000004000081A40000000000000000000000016627DD8100000253000000000000000000000000000000000000004B00000000google-authenticator-libpam-1.10/.github/ISSUE_TEMPLATE/feature_request.md--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. 07070100000005000081A40000000000000000000000016627DD8100000294000000000000000000000000000000000000005700000000google-authenticator-libpam-1.10/.github/ISSUE_TEMPLATE/totp-codes-are-not-accepted.md--- name: TOTP codes are not accepted about: OTP codes are configured, but don't seem to be accepted, or only sometimes accepted title: 'TOTP not accepted: ' labels: '' assignees: '' --- ## Prerequisite Read [this](https://twitter.com/thomashabets/status/1133780752582217728), have a little chuckle to yourself. After that: no seriously really do confirm it. Most of the reported issues with TOTP follow that pattern. ## PAM config E.g. `/etc/pam.d/ssh`, or `common-auth` if it uses that. ## SSH config (or equivalent if not using SSH) `/etc/ssh/sshd_config` ## Enable `debug` on the module, and paste what's logged Maybe from `/var/log/auth.log`. 07070100000006000081A40000000000000000000000016627DD8100001093000000000000000000000000000000000000002C00000000google-authenticator-libpam-1.10/.gitignore### Project specific ignores .deps .dirstamp cmake-build-debug libtool contrib/rpm.spec ### Project binaries google-authenticator examples/demo .libs ### Project tests test-suite.log tests/pam_google_authenticator_unittest tests/*.log tests/*.trs ### Autotools template # http://www.gnu.org/software/automake Makefile.in /ar-lib /mdate-sh /py-compile /test-driver /ylwrap # http://www.gnu.org/software/autoconf /autom4te.cache /autoscan.log /autoscan-*.log /aclocal.m4 /compile /config.guess /config.h.in /config.h /config.log /config.sub /config.status /configure /configure.scan /depcomp /install-sh /missing /stamp-h1 # https://www.gnu.org/software/libtool/ /ltmain.sh # http://www.gnu.org/software/texinfo /texinfo.tex ### C++ template # Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 .idea/ ## File-based project format: *.iws *.iml ## Plugin-specific files: # IntelliJ /out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties ### Archives template # It's better to unpack these files and commit the raw source because # git has its own built in compression methods. *.7z *.jar *.rar *.zip *.gz *.bzip *.bz2 *.xz *.lzma *.cab #packing-only formats *.iso *.tar #package management formats *.dmg *.xpi *.gem *.egg *.deb *.rpm *.msi *.msm *.msp ### Xcode template # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xccheckout *.xcscmblueprint ### macOS template *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Eclipse template .metadata bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .settings/ .loadpath .recommenders # Eclipse Core .project # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # PyDev specific (Python IDE for Eclipse) *.pydevproject # CDT-specific (C/C++ Development Tooling) .cproject # JDT-specific (Eclipse Java Development Tools) .classpath # Java annotation processor (APT) .factorypath # PDT-specific (PHP Development Tools) .buildpath # sbteclipse plugin .target # Tern plugin .tern-project # TeXlipse plugin .texlipse # STS (Spring Tool Suite) .springBeans # Code Recommenders .recommenders/ # Scala IDE specific (Scala & Java development for Eclipse) .cache-main .scala_dependencies .worksheet ### C template # Prerequisites # Object files *.ko *.elf # Linker output *.ilk *.map *.exp # Precompiled Headers # Libraries # Shared objects (inc. Windows DLLs) *.so.* # Executables *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # Kernel Module Compile Results *.mod* *.cmd modules.order Module.symvers Mkfile.old dkms.conf ### CMake template CMakeCache.txt CMakeFiles CMakeLists.txt CMakeScripts Testing Makefile cmake_install.cmake install_manifest.txt compile_commands.json CTestTestfile.cmake ### NetBeans template nbproject/private/ nbbuild/ dist/ nbdist/ .nb-gradle/ ### Vim template # swap [._]*.s[a-v][a-z] [._]*.sw[a-p] [._]s[a-v][a-z] [._]sw[a-p] # session Session.vim # temporary .netrwhist *~ # auto-generated tag files tags base32 07070100000007000081A40000000000000000000000016627DD81000000B7000000000000000000000000000000000000002D00000000google-authenticator-libpam-1.10/.travis.ymllanguage: c compiler: - clang - gcc script: ./bootstrap.sh && ./configure && make && make check sudo: false addons: apt: packages: - libpam0g-dev - libqrencode3 07070100000008000081A40000000000000000000000016627DD81000005AA000000000000000000000000000000000000003100000000google-authenticator-libpam-1.10/CONTRIBUTING.mdWant to contribute? Great! First, read this page (including the small print at the end). ### Before you contribute Before we can use your code, you must sign the [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) (CLA), which you can do online. The CLA is necessary mainly because you own the copyright to your changes, even after your contribution becomes part of our codebase, so we need your permission to use and distribute your code. We also need to be sure of various other things—for instance that you'll tell us if you know that your code infringes on other people's patents. You don't have to sign the CLA until after you've submitted your code for review and a member has approved it, but you must do it before we can put your code into our codebase. Before you start working on a larger contribution, you should get in touch with us first through the issue tracker with your idea so that we can help out and possibly guide you. Coordinating up front makes it much easier to avoid frustration later on. ### Code reviews All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. ### The small print Contributions made by corporations are covered by a different agreement than the one above, the [Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate). 07070100000009000081A40000000000000000000000016627DD8100000BA3000000000000000000000000000000000000002C00000000google-authenticator-libpam-1.10/FILEFORMATAll configuration data and state is kept in ~/.google_authenticator The file is all ASCII, but is kept in a very simple-to-parse and rigid file format. The file size is currently limited to 1kB. This should be generous even when using a very large list of scratch codes. The first line is the base32 encoded secret. It uses characters in the range A..Z2..7. The secret is used after first trying any one-time scratch codes. HOTP mode is used only if HOTP_COUNTER is present. TOTP mode is used only if TOTP_AUTH is present *and* HOTP_COUNTER is not present. The following lines are optional. They all start with a double quote character, followed by a space character. Followed by an option name. Option names are all upper-case and must include an underscore. This ensures that they cannot accidentally appear anywhere else in the file. Options can be followed by option-specific parameters. Currently, the following options are recognized: DISALLOW_REUSE if present, this signals that a time-based token can be used at most once. Any attempt to log in using the same token will be denied. This means that users can typically not log in faster than once every ~30 seconds. The option is followed by a space-separated list of time stamps that have previously been used for login attempts. This option has no effect when HOTP_COUNTER is present. RATE_LIMIT n m ... this optional parameter restricts the number of logins to at most "n" within each "m" second interval. Additional parameters in this line are undocumented; they are used internally to keep track of state. TOTP_AUTH the presence of this option indicates that the secret can be used to authenticate users with a time-based token. STEP_SIZE n the number of seconds in each time step during which a TOTP code is valid. The default is that a code is valid for 30 seconds. HOTP_COUNTER n the presence of this option indicates that the secret can be used to authenticate users with a counter-based token. The argument "n" represents which counter value the token will accept next. It should be initialized to 1. WINDOW_SIZE n the default window size is 3, allowing up to one extra valid token before and after the currently active one. This might be too restrictive if the client and the server experience significant time skew. You can provide a parameter to increase the login window size from 3 to "n" In counter-based mode, this option is the number of valid tokens after the currently active one. The default is almost certainly too restrictive for most users as invalid login attempts and generated-but-not-used tokens both contribute to synchronization problems. Any all-numeric sequence of eight-digit numbers are randomly generated one-time scratch code tokens. The user can enter any arbitrary one-time code to log into his account. The code will then be removed from the file. 0707010000000A000081A40000000000000000000000016627DD8100002C5E000000000000000000000000000000000000002900000000google-authenticator-libpam-1.10/LICENSE Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 0707010000000B000081A40000000000000000000000016627DD81000009B5000000000000000000000000000000000000002D00000000google-authenticator-libpam-1.10/Makefile.amAUTOMAKE_OPTIONS = foreign subdir-objects ACLOCAL_AMFLAGS = -I build pamdir = $(libdir)/security bin_PROGRAMS = google-authenticator noinst_PROGRAMS = base32 dist_man_MANS = man/google-authenticator.1 dist_man_MANS += man/pam_google_authenticator.8 pam_LTLIBRARIES = pam_google_authenticator.la dist_doc_DATA = FILEFORMAT README.md dist_html_DATA = totp.html MODULES_LDFLAGS = -avoid-version -module -shared -export-dynamic CORE_SRC = src/util.h src/util.c CORE_SRC += src/base32.h src/base32.c CORE_SRC += src/hmac.h src/hmac.c CORE_SRC += src/sha1.h src/sha1.c base32_SOURCES=\ src/base32.c \ src/base32_prog.c google_authenticator_SOURCES = \ src/google-authenticator.c \ $(CORE_SRC) pam_google_authenticator_la_SOURCES = \ src/pam_google_authenticator.c \ $(CORE_SRC) pam_google_authenticator_la_LIBADD = -lpam pam_google_authenticator_la_CFLAGS = $(AM_CFLAGS) pam_google_authenticator_la_LDFLAGS = $(AM_LDFLAGS) $(MODULES_LDFLAGS) -export-symbols-regex "pam_sm_(setcred|open_session|authenticate)" check_PROGRAMS = examples/demo tests/pam_google_authenticator_unittest check_LTLIBRARIES = libpam_google_authenticator_testing.la TESTS = tests/pam_google_authenticator_unittest tests/base32_test.sh EXTRA_DIST = tests/base32_test.sh libpam_google_authenticator_testing_la_SOURCES = \ src/pam_google_authenticator.c \ $(CORE_SRC) libpam_google_authenticator_testing_la_CFLAGS = $(AM_CFLAGS) -DTESTING=1 libpam_google_authenticator_testing_la_LDFLAGS = $(AM_LDFLAGS) $(MODULES_LDFLAGS) -rpath $(abs_top_builddir) -lpam tests_pam_google_authenticator_unittest_SOURCES = \ tests/pam_google_authenticator_unittest.c \ $(CORE_SRC) tests_pam_google_authenticator_unittest_LDADD = -lpam tests_pam_google_authenticator_unittest_LDFLAGS = $(AM_LDFLAGS) -export-dynamic test: check examples_demo_SOURCES = \ src/pam_google_authenticator.c \ $(CORE_SRC) \ examples/demo.c examples_demo_LDADD = -lpam examples_demo_CFLAGS = $(AM_CFLAGS) -DDEMO=1 super-clean: maintainer-clean rm -fr aclocal autom4te.cache/ m4 missing libtool config.guess rm -fr config.lt config.status config.sub configure depcomp rm -fr libtool install-sh *~ Makefile aclocal.m4 config.h.in ltmain.sh rm -fr Makefile.in test-driver compile doc: (cd man && pandoc --standalone --to man google-authenticator.1.md > google-authenticator.1) (cd man && pandoc --standalone --to man pam_google_authenticator.8.md > pam_google_authenticator.8) 0707010000000C000081A40000000000000000000000016627DD81000022F7000000000000000000000000000000000000002B00000000google-authenticator-libpam-1.10/README.md# Google Authenticator PAM module Example PAM module demonstrating two-factor authentication for logging into servers via SSH, OpenVPN, etc… This project is not about logging in to Google, Facebook, or other TOTP/HOTP second factor systems, even if they recommend using the Google Authenticator apps. HMAC-Based One-time Password (HOTP) is specified in [RFC 4226](https://tools.ietf.org/html/rfc4226) and Time-based One-time Password (TOTP) is specified in [RFC 6238](https://tools.ietf.org/html/rfc6238). [![Build Status](https://travis-ci.org/google/google-authenticator-libpam.svg?branch=master)](https://travis-ci.org/google/google-authenticator-libpam) ## Build & install ```shell ./bootstrap.sh ./configure make sudo make install ``` If you don't have access to "sudo", you have to manually become "root" prior to calling "make install". ## Setting up the PAM module for your system For highest security, make sure that both password and OTP are being requested even if password and/or OTP are incorrect. This means that *at least* the first of `pam_unix.so` (or whatever other module is used to verify passwords) and `pam_google_authenticator.so` should be set as `required`, not `requisite`. It probably can't hurt to have both be `required`, but it could depend on the rest of your PAM config. If you use HOTP (counter based as opposed to time based) then add the option `no_increment_hotp` to make sure the counter isn't incremented for failed attempts. Add this line to your PAM configuration file: ` auth required pam_google_authenticator.so no_increment_hotp` ## Setting up a user Run the `google-authenticator` binary to create a new secret key in your home directory. These settings will be stored in `~/.google_authenticator`. If your system supports the "libqrencode" library, you will be shown a QRCode that you can scan using the Android "Google Authenticator" application. If your system does not have this library, you can either follow the URL that `google-authenticator` outputs, or you have to manually enter the alphanumeric secret key into the Android "Google Authenticator" application. In either case, after you have added the key, click-and-hold until the context menu shows. Then check that the key's verification value matches (this feature might not be available in all builds of the Android application). Each time you log into your system, you will now be prompted for your TOTP code (time based one-time-password) or HOTP (counter-based), depending on options given to `google-authenticator`, after having entered your normal user id and your normal UNIX account password. During the initial roll-out process, you might find that not all users have created a secret key yet. If you would still like them to be able to log in, you can pass the "nullok" option on the module's command line: ` auth required pam_google_authenticator.so nullok` ## Encrypted home directories If your system encrypts home directories until after your users entered their password, you either have to re-arrange the entries in the PAM configuration file to decrypt the home directory prior to asking for the OTP code, or you have to store the secret file in a non-standard location: ` auth required pam_google_authenticator.so secret=/var/unencrypted-home/${USER}/.google_authenticator` would be a possible choice. Make sure to set appropriate permissions. You also have to tell your users to manually move their .google_authenticator file to this location. In addition to "${USER}", the `secret=` option also recognizes both "~" and `${HOME}` as short-hands for the user's home directory. When using the `secret=` option, you might want to also set the `user=` option. The latter forces the PAM module to switch to a dedicated hard-coded user id prior to doing any file operations. When using the `user=` option, you must not include "~" or "${HOME}" in the filename. The `user=` option can also be useful if you want to authenticate users who do not have traditional UNIX accounts on your system. ## Module options ### secret=/path/to/secret/file See "encrypted home directories", above. ### authtok_prompt=prompt Overrides default token prompt. If you want to include spaces in the prompt, wrap the whole argument in square brackets: ` auth required pam_google_authenticator.so [authtok_prompt=Your secret token: ]` ### user=some-user Force the PAM module to switch to a hard-coded user id prior to doing any file operations. Commonly used with `secret=`. ### no_strict_owner DANGEROUS OPTION! By default the PAM module requires that the secrets file must be owned the user logging in (or if `user=` is specified, owned by that user). This option disables that check. This option can be used to allow daemons not running as root to still handle configuration files not owned by that user, for example owned by the users themselves. ### allowed_perm=0nnn DANGEROUS OPTION! By default, the PAM module requires the secrets file to be readable only by the owner of the file (mode 0600 by default). In situations where the module is used in a non-default configuration, an administrator may need more lenient file permissions, or a specific setting for their use case. ### debug Enable more verbose log messages in syslog. ### try_first_pass / use_first_pass / forward_pass Some PAM clients cannot prompt the user for more than just the password. To work around this problem, this PAM module supports stacking. If you pass the `forward_pass` option, the `pam_google_authenticator` module queries the user for both the system password and the verification code in a single prompt. It then forwards the system password to the next PAM module, which will have to be configured with the `use_first_pass` option. In turn, `pam_google_authenticator` module also supports both the standard `use_first_pass` and `try_first_pass` options. But most users would not need to set those on the `pam_google_authenticator`. ### noskewadj If you discover that your TOTP code never works, this is most commonly the result of the clock on your server being different from the one on your Android device. The PAM module makes an attempt to compensate for time skew. You can teach it about the amount of skew that you are experiencing, by trying to log it three times in a row. Make sure you always wait 30s (but not longer), so that you get three distinct TOTP codes. Some administrators prefer that time skew isn't adjusted automatically, as doing so results in a slightly less secure system configuration. If you want to disable it, you can do so on the module command line: ` auth required pam_google_authenticator.so noskewadj` ### no_increment_hotp Don't increment the counter for failed HOTP attempts. Normally you should set this so failed password attempts by an attacker without a token don't lock out the authorized user. ### nullok Allow users to log in without OTP, if they haven't set up OTP yet. PAM requires at least one `SUCCESS` answer from a module, and `nullok` causes this module to say `IGNORE`. This means that if this option is used at least one other module must have said `SUCCESS`. One way to do this is to add `auth required pam_permit.so` to the end of the PAM config. ### echo_verification_code By default, the PAM module does not echo the verification code when it is entered by the user. In some situations, the administrator might prefer a different behavior. Pass the `echo_verification_code` option to the module in order to enable echoing. If you would like verification codes that are counter based instead of timebased, use the `google-authenticator` binary to generate a secret key in your home directory with the proper option. In this mode, clock skew is irrelevant and the window size option now applies to how many codes beyond the current one that would be accepted, to reduce synchronization problems. ### grace_period=seconds If present and non-zero, provide a grace period during which a second verification code will not be requested. Try setting seconds to 86400 to allow a full-day between requesting codes; or 3600 for an hour. This works by adding an (IP address, timestamp) pair to the security file after a successful one-time-password login; only the last ten distinct IP addresses are tracked. ### allow_readonly DANGEROUS OPTION! With this option an attacker with ability to fill up the filesystem (flood server with web requests, or if they have an account just fill the disk up) can force a situation where one-time-passwords can be reused, defeating the purpose of "one time". By default, if the `grace_period` option is defined the PAM module requires some free space to store the IP address and timestamp of the last login. It could prevent access if a server has no free space or in case of an update config file error. With the `allow_readonly` option you can ignore any errors which could occur during config file update. 0707010000000D000081ED0000000000000000000000016627DD810000025D000000000000000000000000000000000000002E00000000google-authenticator-libpam-1.10/bootstrap.sh#!/bin/sh # Copyright 2014 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. exec autoreconf -i 0707010000000E000041ED0000000000000000000000026627DD8100000000000000000000000000000000000000000000002700000000google-authenticator-libpam-1.10/build0707010000000F000081A40000000000000000000000016627DD810000000E000000000000000000000000000000000000003200000000google-authenticator-libpam-1.10/build/.gitignore* !.gitignore 07070100000010000081A40000000000000000000000016627DD8100000907000000000000000000000000000000000000002E00000000google-authenticator-libpam-1.10/configure.acAC_PREREQ(2.61) AC_INIT(google-authenticator, 1.10, habets@google.com) AC_CONFIG_SRCDIR([src/google-authenticator.c]) AC_CONFIG_AUX_DIR([build]) AC_CONFIG_MACRO_DIR([build]) # --enable-silent-rules m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])], [AC_SUBST([AM_DEFAULT_VERBOSITY], [1])]) AC_USE_SYSTEM_EXTENSIONS AM_INIT_AUTOMAKE([foreign subdir-objects]) AM_MAINTAINER_MODE([enable]) LT_INIT AC_PROG_CC AC_PROG_CC_STDC AC_CHECK_HEADERS([sys/fsuid.h]) AC_CHECK_FUNCS([ \ explicit_bzero \ setfsuid \ setfsgid \ ]) AC_CHECK_HEADERS_ONCE([security/pam_appl.h]) # On Solaris at least, <security/pam_modules.h> requires <security/pam_appl.h> # to be included first AC_CHECK_HEADER([security/pam_modules.h], [], [], \ [#ifdef HAVE_SECURITY_PAM_APPL_H # include <security/pam_appl.h> #endif ]) AC_CHECK_LIB([pam], [pam_get_user], [:]) AS_IF([test "x$ac_cv_header_security_pam_modules_h" = "xno" \ -o "x$ac_cv_lib_pam_pam_get_user" = "xno"], [ AC_MSG_ERROR([Unable to find the PAM library or the PAM header files]) ]) AC_MSG_CHECKING([whether certain PAM functions require const arguments]) AC_LANG_PUSH(C) # Force test to bail if const isn't needed AC_LANG_WERROR AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include <security/pam_appl.h> #include <security/pam_modules.h> ]],[[ const void **item = 0; int dummy = 0; /* * since pam_handle_t is opaque on at least some platforms, give it * a non-NULL dummy value */ const pam_handle_t *ph = (const pam_handle_t *)&dummy; (void) pam_get_item(ph, 0, item); ]])],[AC_DEFINE([PAM_CONST], [const], \ [Define if certain PAM functions require const arguments]) AC_MSG_RESULT([yes])], [AC_DEFINE([PAM_CONST], [], \ [Prevent certain PAM functions from using const arguments]) AC_MSG_RESULT([no])]) AC_MSG_CHECKING(whether compiler understands -Wall) old_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -Wall" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], AC_MSG_RESULT(yes), AC_MSG_RESULT(no) CFLAGS="$old_CFLAGS") AC_LANG_POP(C) AC_SEARCH_LIBS([dlopen], [dl]) AC_CONFIG_HEADER(config.h) AC_CONFIG_FILES([Makefile contrib/rpm.spec]) AC_OUTPUT echo " $PACKAGE_NAME version $PACKAGE_VERSION Prefix.........: $prefix Debug Build....: $debug C Compiler.....: $CC $CFLAGS $CPPFLAGS Linker.........: $LD $LDFLAGS $LIBS " 07070100000011000041ED0000000000000000000000026627DD8100000000000000000000000000000000000000000000002900000000google-authenticator-libpam-1.10/contrib07070100000012000081A40000000000000000000000016627DD8100000520000000000000000000000000000000000000003700000000google-authenticator-libpam-1.10/contrib/README.rpm.md## Building a Google-Authenticator RPM Please note the RPM does not require QR-Encode as a dependency, As technically the module builds fine without it. But in all likely- hood you will need it in an actual deployment. Building a QR-Encode RPM is outside the scope of this documentation, see the in-repo documentation for instructions. https://github.com/fukuchi/libqrencode If you are using RPMs in your testing a new build number option has been added to the spec file IE: --define '_release #' to where # is a build number. This will generate a RPM in the namespace: google-authenticator-1.01-#.el6.x86_64.rpm where # is your specified build number. If no _release is set the build is defaulted to 1. Example: ``` rpmbuild -ba contrib/rpm.spec --define '_release 8' ``` This will generate an rpm of: ``` google-authenticator-1.01-8.el6.x86_64.rpm ``` ### Requirements * gcc * libtool * autoconf * automake * libpam-devel * rpm-builder * qr-encode (optional) ### Process ```shell git clone https://github.com/google/google-authenticator.git cd google-authenticator/libpam ./bootstrap.sh ./configure make dist cp google-autheticator-#.##.tar.gz ~/rpmbuild/SOURCES/ rpmbuild -ba contrib/rpm.spec ``` The script build-rpm.sh has been created to make these steps a bit easier to perform. 07070100000013000081ED0000000000000000000000016627DD8100000323000000000000000000000000000000000000003600000000google-authenticator-libpam-1.10/contrib/build-rpm.sh#!/bin/bash # # A simple script to make building an RPM of the software a lot easier # set -e if [ "$(which rpmbuild)" == "" ]; then echo "To build an rpm the tool rpmbuild needs to be installed first" exit 1 fi DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if [[ -z $1 ]]; then echo "Usage $0 \"release\"" exit 1 fi cd "${DIR}/.." echo "Starting build" ./bootstrap.sh && \ ./configure && \ make dist && \ ( mkdir -p "${DIR}/_rpmbuild/SOURCES" cp -f google-authenticator-*.tar.gz "${DIR}/_rpmbuild/SOURCES/" rpmbuild -ba contrib/rpm.spec --define "_topdir ${DIR}/_rpmbuild" --define "_release $1" echo "==============" echo "Available RPMs" find "${DIR}/_rpmbuild/" -type f -name 'google-authenticator*.rpm' ) || (echo "Something went wrong"; exit 1) exit 0 07070100000014000081A40000000000000000000000016627DD810000061B000000000000000000000000000000000000003500000000google-authenticator-libpam-1.10/contrib/rpm.spec.inName: @PACKAGE_NAME@ Version: @VERSION@ %if %{?_release:1}0 Release: %{_release}%{?dist} %else Release: 1%{?dist} %endif Summary: One-time passcode support using open standards License: ASL 2.0 URL: https://github.com/google/google-authenticator Source0: %{name}-%{version}.tar.gz BuildRequires: gcc BuildRequires: pam-devel BuildRequires: libtool %description The Google Authenticator package contains a pluggable authentication module (PAM) which allows login using one-time passcodes conforming to the open standards developed by the Initiative for Open Authentication (OATH) (which is unrelated to OAuth). Passcode generators are available (separately) for several mobile platforms. These implementations support the HMAC-Based One-time Password (HOTP) algorithm specified in RFC 4226 and the Time-based One-time Password (TOTP) algorithm specified in RFC 6238. %prep %setup -q %build %configure --libdir=/%{_lib} make %{?_smp_mflags} %install rm -rf $RPM_BUILD_ROOT make install DESTDIR=$RPM_BUILD_ROOT rm $RPM_BUILD_ROOT/%{_lib}/security/pam_google_authenticator.la %files /%{_lib}/security/pam_google_authenticator.so %{_bindir}/%{name} %{_defaultdocdir}/%{name}/README.md %{_defaultdocdir}/%{name}/totp.html %{_defaultdocdir}/%{name}/FILEFORMAT %{_mandir}/man1/* %{_mandir}/man8/* %changelog * Fri Aug 18 2017 Niels Basjes <niels@basjes.nl> - 1.04 - Added installing man pages * Wed Jan 13 2016 Dan Molik <dan@d3fy.net> - 1.01 - A new and updated build for google-authenticator 07070100000015000041ED0000000000000000000000026627DD8100000000000000000000000000000000000000000000002A00000000google-authenticator-libpam-1.10/examples07070100000016000081A40000000000000000000000016627DD810000137F000000000000000000000000000000000000003100000000google-authenticator-libpam-1.10/examples/demo.c// Demo wrapper for the PAM module. This is part of the Google Authenticator // project. // // Copyright 2011 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "config.h" #include <assert.h> #include <fcntl.h> #include <security/pam_appl.h> #include <security/pam_modules.h> #include <setjmp.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/resource.h> #include <sys/time.h> #include <termios.h> #include <unistd.h> #if !defined(PAM_BAD_ITEM) // FreeBSD does not know about PAM_BAD_ITEM. And PAM_SYMBOL_ERR is an "enum", // we can't test for it at compile-time. #define PAM_BAD_ITEM PAM_SYMBOL_ERR #endif static struct termios old_termios; static int jmpbuf_valid; static sigjmp_buf jmpbuf; static int conversation(int num_msg, PAM_CONST struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { if (num_msg == 1 && (msg[0]->msg_style == PAM_PROMPT_ECHO_OFF || msg[0]->msg_style == PAM_PROMPT_ECHO_ON)) { *resp = malloc(sizeof(struct pam_response)); assert(*resp); (*resp)->resp = calloc(1024, 1); struct termios termios = old_termios; if (msg[0]->msg_style == PAM_PROMPT_ECHO_OFF) { termios.c_lflag &= ~(ECHO|ECHONL); } sigsetjmp(jmpbuf, 1); jmpbuf_valid = 1; sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGTSTP); assert(!sigprocmask(SIG_UNBLOCK, &mask, NULL)); printf("%s ", msg[0]->msg); assert(!tcsetattr(0, TCSAFLUSH, &termios)); assert(fgets((*resp)->resp, 1024, stdin)); assert(!tcsetattr(0, TCSAFLUSH, &old_termios)); puts(""); assert(!sigprocmask(SIG_BLOCK, &mask, NULL)); jmpbuf_valid = 0; char *ptr = strrchr((*resp)->resp, '\n'); if (ptr) { *ptr = '\000'; } (*resp)->resp_retcode = 0; return PAM_SUCCESS; } if (num_msg == 1 && msg[0]->msg_style == PAM_ERROR_MSG) { printf("Error message to user: %s\n", msg[0]->msg); return PAM_SUCCESS; } return PAM_CONV_ERR; } #ifdef sun #define PAM_CONST #else #define PAM_CONST const #endif int pam_get_user(pam_handle_t *pamh, PAM_CONST char **user, const char *prompt) { return pam_get_item(pamh, PAM_USER, (void *)user); } int pam_get_item(const pam_handle_t *pamh, int item_type, PAM_CONST void **item) { switch (item_type) { case PAM_SERVICE: { static const char service[] = "google_authenticator_demo"; *item = service; return PAM_SUCCESS; } case PAM_USER: { char *user = getenv("USER"); *item = user; return PAM_SUCCESS; } case PAM_CONV: { static struct pam_conv conv = { .conv = conversation }, *p_conv = &conv; *item = p_conv; return PAM_SUCCESS; } default: return PAM_BAD_ITEM; } } int pam_set_item(pam_handle_t *pamh, int item_type, const void *item) { switch (item_type) { case PAM_AUTHTOK: return PAM_SUCCESS; default: return PAM_BAD_ITEM; } } static void print_diagnostics(int signo) { extern const char *get_error_msg(void); assert(!tcsetattr(0, TCSAFLUSH, &old_termios)); fprintf(stderr, "%s\n", get_error_msg()); _exit(1); } static void reset_console(int signo) { assert(!tcsetattr(0, TCSAFLUSH, &old_termios)); puts(""); _exit(1); } static void stop(int signo) { assert(!tcsetattr(0, TCSAFLUSH, &old_termios)); puts(""); raise(SIGSTOP); } static void cont(int signo) { if (jmpbuf_valid) { siglongjmp(jmpbuf, 0); } } int main(int argc, char *argv[]) { extern int pam_sm_authenticate(pam_handle_t *, int, int, const char **); // Try to redirect stdio to /dev/tty int fd = open("/dev/tty", O_RDWR); if (fd >= 0) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); } // Disable core files assert(!setrlimit(RLIMIT_CORE, (struct rlimit []){ { 0, 0 } })); // Set up error and job control handlers assert(!tcgetattr(0, &old_termios)); sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGTSTP); assert(!sigprocmask(SIG_BLOCK, &mask, NULL)); assert(!signal(SIGABRT, print_diagnostics)); assert(!signal(SIGINT, reset_console)); assert(!signal(SIGTSTP, stop)); assert(!signal(SIGCONT, cont)); // Attempt login if (pam_sm_authenticate(NULL, 0, argc-1, (const char **)argv+1) != PAM_SUCCESS) { fprintf(stderr, "Login failed\n"); abort(); } return 0; } 07070100000017000041ED0000000000000000000000026627DD8100000000000000000000000000000000000000000000002500000000google-authenticator-libpam-1.10/man07070100000018000081A40000000000000000000000016627DD8100001789000000000000000000000000000000000000003C00000000google-authenticator-libpam-1.10/man/google-authenticator.1.\" Automatically generated by Pandoc 1.16.0.2 .\" .TH "GOOGLE\-AUTHENTICATOR" "1" "" "Google two\-factor authentication user manual" "" .hy .SH NAME .PP google\-authenticator \- initialize one\-time passcodes for the current user .SH SYNOPSIS .PP google\-authenticator [\f[I]options\f[]] .PP If no option is provided on the command line, google\-authenticator(1) will ask interactively the user for the more important options. .SH DESCRIPTION .PP The google\-authenticator(1) command creates a new secret key in the current user\[aq]s home directory. By default, this secret key and all settings will be stored in \f[C]~/.google_authenticator\f[]. .PP If the system supports the \f[C]libqrencode\f[] library, a QRCode will be shown, that can be scanned using the Android Google Authenticator application. If the system does not have this library, google\-authenticator(1) outputs an URL that can be followed using a web browser. Alternatively, the alphanumeric secret key is also outputted and thus can be manually entered into the Android Google Authenticator application. .PP In either case, after the key has been added, the verification value should be checked. To do that, the user must click\-and\-hold the added entry on its Android system until the context menu shows. Then, the user checks that the displayed key\[aq]s verification value matches the one provided by google\-authenticator(1). Please note that this feature might not be available in all builds of the Android application. .PP Each time the user logs into the system, he will now be prompted for the TOTP code (time based one\-time\-password) or HOTP (counter\-based one\-time\-password), depending on options given to google\-authenticator(1), after having entered its normal user id and its normal UNIX account password. .SH OPTIONS .PP The main option consists of choosing the authentication token type: either time based or counter\-based. .TP .B \-c, \-\-counter\-based Set up counter\-based verification. .RS .RE .TP .B \-t, \-\-time\-based Set up time\-based verification. .RS .RE .PP From this choice depends the available options. .SS Counter\-based specific options .PP Those settings are only relevant for counter\-based one\-time\-password (HOTP): .TP .B \-w, \-\-window\-size=\f[I]W\f[] Set window of concurrently valid codes. .RS .PP By default, three tokens are valid at any one time. This accounts for generated\-but\-not\-used tokens and failed login attempts. In order to decrease the likelihood of synchronization problems, this window can be increased from its default size of 3. .PP The window size must be between 1 and 21. .RE .TP .B \-W, \-\-minimal\-window Disable window of concurrently valid codes. .RS .RE .SS Time\-based specific options .PP Those settings are only relevant for time\-based one\-time\-password (TOTP): .TP .B \-D, \-\-allow\-reuse, \-d, \-\-disallow\-reuse (Dis)allow multiple uses of the same authentication token. .RS .PP This restricts the user to one login about every 30 seconds, but it increases the chances to notice or even prevent man\-in\-the\-middle attacks. .RE .TP .B \-w, \-\-window\-size=\f[I]W\f[] Set window of concurrently valid codes. .RS .PP By default, a new token is generated every 30 seconds by the mobile application. In order to compensate for possible time\-skew between the client and the server, an extra token before and after the current time is allowed. This allows for a time skew of up to 30 seconds between authentication server and client. .PP For example, if problems with poor time synchronization are experienced, the window can be increased from its default size of 3 permitted codes (one previous code, the current code, the next code) to 17 permitted codes (the 8 previous codes, the current code, and the 8 next codes). This will permit for a time skew of up to 4 minutes between client and server. .PP The window size must be between 1 and 21. .RE .TP .B \-W, \-\-minimal\-window Disable window of concurrently valid codes. .RS .RE .TP .B \-S, \-\-step\-size=\f[I]S\f[] Set interval between token refreshes to \f[I]S\f[] seconds. .RS .PP By default, time\-based tokens are generated every 30 seconds. A non\-standard value can be configured in case a different time\-step value must be used. .PP The time interval must be between 1 and 60 seconds. .RE .SS General options .TP .B \-s, \-\-secret=\f[I]FILE\f[] Specify a non\-standard file location for the secret key and settings. .RS .RE .TP .B \-f, \-\-force Write secret key and settings without first confirming with user. .RS .RE .TP .B \-l, \-\-label=\f[I]LABEL\f[] Override the default label in \f[C]otpauth://\f[] URL. .RS .RE .TP .B \-i, \-\-issuer=\f[I]ISSUER\f[] Override the default issuer in \f[C]otpauth://\f[] URL. .RS .RE .TP .B \-Q, \-\-qr\-mode=\f[I]none|ansi|utf8\f[] QRCode output mode. .RS .PP Suppress the QRCode output (\f[I]none\f[]), or output QRCode using either ANSI colors (\f[I]ansi\f[]), or Unicode block elements (\f[I]utf8\f[]). .PP Unicode block elements makes the QRCode much smaller, which is often easier to scan. Unfortunately, many terminal emulators do not display these Unicode characters properly. .RE .TP .B \-r, \-\-rate\-limit=\f[I]N\f[], \-R, \-\-rate\-time=\f[I]M\f[], \-u, \-\-no\-rate\-limit Disable rate\-limiting, or limit logins to N per every M seconds. .RS .PP If the system isn\[aq]t hardened against brute\-force login attempts, rate\-limiting can be enabled for the authentication module: no more than \f[I]N\f[] login attempts every \f[I]M\f[] seconds. .PP The rate limit must be between 1 and 10 attempts. The rate time must be between 15 and 600 seconds. .RE .TP .B \-e, \-\-emergency\-codes=\f[I]N\f[] Generate \f[I]N\f[] emergency codes. .RS .PP A maximum of 10 emergency codes can be generated. .RE .TP .B \-q, \-\-quiet Quiet mode. .RS .RE .TP .B \-h, \-\-help Print the help message. .RS .RE .SH SEE ALSO .PP The Google Authenticator source code and all documentation may be downloaded from <https://github.com/google/google-authenticator-libpam>. 07070100000019000081A40000000000000000000000016627DD81000015B3000000000000000000000000000000000000003F00000000google-authenticator-libpam-1.10/man/google-authenticator.1.md% GOOGLE-AUTHENTICATOR(1) Google two-factor authentication user manual # NAME google-authenticator - initialize one-time passcodes for the current user # SYNOPSIS google-authenticator [*options*] If no option is provided on the command line, google-authenticator(1) will ask interactively the user for the more important options. # DESCRIPTION The google-authenticator(1) command creates a new secret key in the current user's home directory. By default, this secret key and all settings will be stored in `~/.google_authenticator`. If the system supports the `libqrencode` library, a QRCode will be shown, that can be scanned using the Android Google Authenticator application. If the system does not have this library, google-authenticator(1) outputs an URL that can be followed using a web browser. Alternatively, the alphanumeric secret key is also outputted and thus can be manually entered into the Android Google Authenticator application. In either case, after the key has been added, the verification value should be checked. To do that, the user must click-and-hold the added entry on its Android system until the context menu shows. Then, the user checks that the displayed key's verification value matches the one provided by google-authenticator(1). Please note that this feature might not be available in all builds of the Android application. Each time the user logs into the system, he will now be prompted for the TOTP code (time based one-time-password) or HOTP (counter-based one-time-password), depending on options given to google-authenticator(1), after having entered its normal user id and its normal UNIX account password. # OPTIONS The main option consists of choosing the authentication token type: either time based or counter-based. -c, --counter-based : Set up counter-based verification. -t, --time-based : Set up time-based verification. From this choice depends the available options. ## Counter-based specific options Those settings are only relevant for counter-based one-time-password (HOTP): -w, --window-size=*W* : Set window of concurrently valid codes. By default, three tokens are valid at any one time. This accounts for generated-but-not-used tokens and failed login attempts. In order to decrease the likelihood of synchronization problems, this window can be increased from its default size of 3. The window size must be between 1 and 21. -W, --minimal-window : Disable window of concurrently valid codes. ## Time-based specific options Those settings are only relevant for time-based one-time-password (TOTP): -D, --allow-reuse, -d, --disallow-reuse : (Dis)allow multiple uses of the same authentication token. This restricts the user to one login about every 30 seconds, but it increases the chances to notice or even prevent man-in-the-middle attacks. -w, --window-size=*W* : Set window of concurrently valid codes. By default, a new token is generated every 30 seconds by the mobile application. In order to compensate for possible time-skew between the client and the server, an extra token before and after the current time is allowed. This allows for a time skew of up to 30 seconds between authentication server and client. For example, if problems with poor time synchronization are experienced, the window can be increased from its default size of 3 permitted codes (one previous code, the current code, the next code) to 17 permitted codes (the 8 previous codes, the current code, and the 8 next codes). This will permit for a time skew of up to 4 minutes between client and server. The window size must be between 1 and 21. -W, --minimal-window : Disable window of concurrently valid codes. -S, --step-size=*S* : Set interval between token refreshes to *S* seconds. By default, time-based tokens are generated every 30 seconds. A non-standard value can be configured in case a different time-step value must be used. The time interval must be between 1 and 60 seconds. ## General options -s, --secret=*FILE* : Specify a non-standard file location for the secret key and settings. -f, --force : Write secret key and settings without first confirming with user. -l, --label=*LABEL* : Override the default label in `otpauth://` URL. -i, --issuer=*ISSUER* : Override the default issuer in `otpauth://` URL. -Q, --qr-mode=*none|ansi|utf8* : QRCode output mode. Suppress the QRCode output (*none*), or output QRCode using either ANSI colors (*ansi*), or Unicode block elements (*utf8*). Unicode block elements makes the QRCode much smaller, which is often easier to scan. Unfortunately, many terminal emulators do not display these Unicode characters properly. -r, --rate-limit=*N*, -R, --rate-time=*M*, -u, --no-rate-limit : Disable rate-limiting, or limit logins to N per every M seconds. If the system isn't hardened against brute-force login attempts, rate-limiting can be enabled for the authentication module: no more than *N* login attempts every *M* seconds. The rate limit must be between 1 and 10 attempts. The rate time must be between 15 and 600 seconds. -e, --emergency-codes=*N* : Generate *N* emergency codes. A maximum of 10 emergency codes can be generated. -q, --quiet : Quiet mode. -h, --help : Print the help message. # SEE ALSO The Google Authenticator source code and all documentation may be downloaded from <https://github.com/google/google-authenticator-libpam>. 0707010000001A000081A40000000000000000000000016627DD8100001A4A000000000000000000000000000000000000004000000000google-authenticator-libpam-1.10/man/pam_google_authenticator.8.\" Automatically generated by Pandoc 1.16.0.2 .\" .TH "PAM_GOOGLE_AUTHENTICATOR" "8" "" "Google Authenticator PAM module manual" "" .hy .SH NAME .PP pam_google_authenticator \- PAM module for Google two\-factor authentication .SH SYNOPSIS .PP \f[B]pam_google_authenticator.so\f[] [secret=\f[I]file\f[]] [authtok_prompt=\f[I]prompt\f[]] [user=\f[I]username\f[]] [no_strict_owner] [allowed_perm=\f[I]0nnn\f[]] [debug] [try_first_pass|use_first_pass|forward_pass] [noskewadj] [no_increment_hotp] [nullok] [echo_verification_code] .SH DESCRIPTION .PP The \f[B]pam_google_authenticator\f[] module is designed to protect user authentication with a second factor, either time\-based (TOTP) or counter\-based (HOTP). Prior logging in, the user will be asked for both its password and a one\-time code. Such one\-time codes can be generated with the Google Authenticator application, installed on the user\[aq]s Android device. To respectively generate and verify those one\-time codes, a secret key (randomly generated) must be shared between the device on which one\-time codes are generated and the system on which this PAM module is enabled. .PP Depending on its configuration (see \f[I]options\f[] section), this module requires that a secret file is manually set up for each account on the system. This secret file holds the secret key and user\-specific options (see \f[B]google\-authenticator\f[](1)). Unless the \f[B]nullok\f[] option is used, authentication tries will be rejected if such secret file doesn\[aq]t exist. Alternatively, a system administrator may create those secret files on behalf of the users and then communicates to them the secret keys. .SH OPTIONS .TP .B secret=\f[I]file\f[] Specify a non\-standard file location for the secret file. .RS .PP By default, the PAM module looks for the secret file in the \f[C]\&.google_authenticator\f[] file within the home of the user logging in. This option overrides this location. .PP The provided location may include the following short\-hands: .IP \[bu] 2 \f[B]${USER}\f[] that will be interpreted as the username. .IP \[bu] 2 \f[B]${HOME}\f[] and \f[B]~\f[] that will be interpreted as the user\[aq]s home directory. .RE .TP .B authtok_prompt=\f[I]prompt\f[] Override default token prompt. .RS .PP Note that if spaces are present in the provided prompt, the whole argument must be wrapped in square brackets. .RE .TP .B user=\f[I]username\f[] Switch to a hard\-coded user prior to doing any file operation. .RS .RE .TP .B no_strict_owner Disable the check against the secret file\[aq]s owner. .RS .PP By default, the secret file must be owned by the user logging in. This option disables this check. .RE .TP .B allowed_perm=\f[I]0nnn\f[] Override checked permissions of the secret file. .RS .PP By default, the secret file must be readable only by its owner (ie. mode \f[I]0600\f[]). This option allows a different mode to be specified for this file. .RE .TP .B debug Enable more verbose log messages in syslog. .RS .RE .TP .B try_first_pass|use_first_pass|forward_pass Stacking options for this PAM module. .RS .PP Because some PAM clients cannot prompt the user for more than just the password, the following stacking options may be used: .IP \[bu] 2 \f[B]try_first_pass\f[]: before prompting the user for the one\-time code, this module first tries the previous stacked module\[aq]s password in case that satisfies this module as well. .IP \[bu] 2 \f[B]use_first_pass\f[]: force this module to use a previous stacked modules password. With this option, this module will never prompt the user for the one\-time code. Thus, if no valid one\-time code is available, the user will be denied access. .IP \[bu] 2 \f[B]forward_pass\f[]: query the user for both the system password and the verification code in a single prompt. The system password is then forwarded to the next PAM module, which will have to be configured with either the \f[B]use_first_pass\f[] option, or the \f[B]try_first_pass\f[] option. .RE .TP .B noskewadj Don\[aq]t adjust time skew automatically. .RS .PP By default, the PAM module makes an attempt to compensate for time skew between the server and the device on which one\-time passcodes are generated. This option disable this behavior. .PP Note that this option is only relevant for time\-based (TOTP) mode. .RE .TP .B no_increment_hotp Don\[aq]t increment the counter for failed attempts. .RS .PP In some circonstance, failed passwords still get an OTP prompt. This option disables counter incrementation is such situations. .PP Note that this option is only relevant for counter\-based (HOTP) mode. .RE .TP .B nullok Allow users to log in without OTP, if they haven\[aq]t set up OTP yet. .RS .PP During the initial roll\-out process, all users may not have created a secret key yet. This option allows them to log in, even if the secret file doesn\[aq]t exist. .RE .TP .B echo_verification_code Echo the verification code when it is entered by the user. .RS .RE .SH MODULE TYPE PROVIDED .PP Only the \f[B]auth\f[] module type is provided. .SH RETURN VALUES .TP .B PAM_SUCCESS Either the provided one\-time code is correct or is a valid emergency code. .RS .RE .TP .B PAM_IGNORE This module is ignored. .RS .RE .TP .B PAM_AUTH_ERR The provided one\-time code isn\[aq]t correct and isn\[aq]t a valid emergency code, or an error was encountered. .RS .RE .SH EXAMPLES .PP The following lines may be used to enable this PAM module: .IP \[bu] 2 \f[C]auth\ required\ pam_google_authenticator.so\ no_increment_hotp\f[] # Make sure the counter (for HOTP mode) isn\[aq]t incremented for failed attempts. .IP \[bu] 2 \f[C]auth\ required\ pam_google_authenticator.so\ nullok\f[] # Allow users to log in if their secret files don\[aq]t exist .IP \[bu] 2 \f[C]auth\ required\ pam_google_authenticator.so\ secret=/var/unencrypted\-home/${USER}/.google_authenticator\f[] # Store secret files in a specific location .IP \[bu] 2 \f[C]auth\ required\ pam_google_authenticator.so\ [authtok_prompt=Your\ secret\ token:\ ]\f[] # Use a specific prompt .IP \[bu] 2 \f[C]auth\ required\ pam_google_authenticator.so\ noskewadj\f[] # Don\[aq]t compensate time skew automatically .SH SECURITY NOTES .PP For highest security, make sure that both password and one\-time code are being requested even if password and/or one\-time code are incorrect. This means that \f[I]at least\f[] the first of \f[C]pam_unix.so\f[] (or whatever other module is used to verify passwords) and \f[C]pam_google_authenticator.so\f[] should be set as \f[B]required\f[], not \f[B]requisite\f[]. .SH SEE ALSO .PP \f[B]google\-authenticator\f[](1). .PP The Google Authenticator source code and all documentation may be downloaded from <https://github.com/google/google-authenticator-libpam>. 0707010000001B000081A40000000000000000000000016627DD810000182B000000000000000000000000000000000000004300000000google-authenticator-libpam-1.10/man/pam_google_authenticator.8.md% PAM_GOOGLE_AUTHENTICATOR(8) Google Authenticator PAM module manual # NAME pam_google_authenticator - PAM module for Google two-factor authentication # SYNOPSIS **pam_google_authenticator.so** [secret=*file*] [authtok_prompt=*prompt*] [user=*username*] [no_strict_owner] [allowed_perm=*0nnn*] [debug] [try_first_pass|use_first_pass|forward_pass] [noskewadj] [no_increment_hotp] [nullok] [echo_verification_code] # DESCRIPTION The **pam_google_authenticator** module is designed to protect user authentication with a second factor, either time-based (TOTP) or counter-based (HOTP). Prior logging in, the user will be asked for both its password and a one-time code. Such one-time codes can be generated with the Google Authenticator application, installed on the user's Android device. To respectively generate and verify those one-time codes, a secret key (randomly generated) must be shared between the device on which one-time codes are generated and the system on which this PAM module is enabled. Depending on its configuration (see *options* section), this module requires that a secret file is manually set up for each account on the system. This secret file holds the secret key and user-specific options (see **google-authenticator**(1)). Unless the **nullok** option is used, authentication tries will be rejected if such secret file doesn't exist. Alternatively, a system administrator may create those secret files on behalf of the users and then communicates to them the secret keys. # OPTIONS secret=*file* : Specify a non-standard file location for the secret file. By default, the PAM module looks for the secret file in the `.google_authenticator` file within the home of the user logging in. This option overrides this location. The provided location may include the following short-hands: - **\${USER}** that will be interpreted as the username. - **\${HOME}** and **~** that will be interpreted as the user's home directory. authtok_prompt=*prompt* : Override default token prompt. Note that if spaces are present in the provided prompt, the whole argument must be wrapped in square brackets. user=*username* : Switch to a hard-coded user prior to doing any file operation. no_strict_owner : Disable the check against the secret file's owner. By default, the secret file must be owned by the user logging in. This option disables this check. allowed_perm=*0nnn* : Override checked permissions of the secret file. By default, the secret file must be readable only by its owner (ie. mode *0600*). This option allows a different mode to be specified for this file. debug : Enable more verbose log messages in syslog. try_first_pass|use_first_pass|forward_pass : Stacking options for this PAM module. Because some PAM clients cannot prompt the user for more than just the password, the following stacking options may be used: - **try_first_pass**: before prompting the user for the one-time code, this module first tries the previous stacked module's password in case that satisfies this module as well. - **use_first_pass**: force this module to use a previous stacked modules password. With this option, this module will never prompt the user for the one-time code. Thus, if no valid one-time code is available, the user will be denied access. - **forward_pass**: query the user for both the system password and the verification code in a single prompt. The system password is then forwarded to the next PAM module, which will have to be configured with either the **use_first_pass** option, or the **try_first_pass** option. noskewadj : Don't adjust time skew automatically. By default, the PAM module makes an attempt to compensate for time skew between the server and the device on which one-time passcodes are generated. This option disable this behavior. Note that this option is only relevant for time-based (TOTP) mode. no_increment_hotp : Don't increment the counter for failed attempts. In some circonstance, failed passwords still get an OTP prompt. This option disables counter incrementation is such situations. Note that this option is only relevant for counter-based (HOTP) mode. nullok : Allow users to log in without OTP, if they haven't set up OTP yet. During the initial roll-out process, all users may not have created a secret key yet. This option allows them to log in, even if the secret file doesn't exist. echo_verification_code : Echo the verification code when it is entered by the user. # MODULE TYPE PROVIDED Only the **auth** module type is provided. # RETURN VALUES PAM_SUCCESS : Either the provided one-time code is correct or is a valid emergency code. PAM_IGNORE : This module is ignored. PAM_AUTH_ERR : The provided one-time code isn't correct and isn't a valid emergency code, or an error was encountered. # EXAMPLES The following lines may be used to enable this PAM module: - `auth required pam_google_authenticator.so no_increment_hotp` # Make sure the counter (for HOTP mode) isn't incremented for failed attempts. - `auth required pam_google_authenticator.so nullok` # Allow users to log in if their secret files don't exist - `auth required pam_google_authenticator.so secret=/var/unencrypted-home/${USER}/.google_authenticator` # Store secret files in a specific location - `auth required pam_google_authenticator.so [authtok_prompt=Your secret token: ]` # Use a specific prompt - `auth required pam_google_authenticator.so noskewadj` # Don't compensate time skew automatically # SECURITY NOTES For highest security, make sure that both password and one-time code are being requested even if password and/or one-time code are incorrect. This means that *at least* the first of `pam_unix.so` (or whatever other module is used to verify passwords) and `pam_google_authenticator.so` should be set as **required**, not **requisite**. # SEE ALSO **google-authenticator**(1). The Google Authenticator source code and all documentation may be downloaded from <https://github.com/google/google-authenticator-libpam>. 0707010000001C000041ED0000000000000000000000026627DD8100000000000000000000000000000000000000000000002500000000google-authenticator-libpam-1.10/src0707010000001D000081A40000000000000000000000016627DD81000009B9000000000000000000000000000000000000002E00000000google-authenticator-libpam-1.10/src/base32.c// Base32 implementation // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include <string.h> #include "base32.h" int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) { unsigned int buffer = 0; int bitsLeft = 0; int count = 0; for (const uint8_t *ptr = encoded; count < bufSize && *ptr; ++ptr) { uint8_t ch = *ptr; if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') { continue; } buffer <<= 5; // Deal with commonly mistyped characters if (ch == '0') { ch = 'O'; } else if (ch == '1') { ch = 'L'; } else if (ch == '8') { ch = 'B'; } // Look up one base32 digit if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { ch = (ch & 0x1F) - 1; } else if (ch >= '2' && ch <= '7') { ch -= '2' - 26; } else { return -1; } buffer |= ch; bitsLeft += 5; if (bitsLeft >= 8) { result[count++] = buffer >> (bitsLeft - 8); bitsLeft -= 8; } } if (count < bufSize) { result[count] = '\000'; } return count; } int base32_encode(const uint8_t *data, int length, uint8_t *result, int bufSize) { if (length < 0 || length > (1 << 28)) { return -1; } int count = 0; if (length > 0) { unsigned int buffer = data[0]; int next = 1; int bitsLeft = 8; while (count < bufSize && (bitsLeft > 0 || next < length)) { if (bitsLeft < 5) { if (next < length) { buffer <<= 8; buffer |= data[next++] & 0xFF; bitsLeft += 8; } else { int pad = 5 - bitsLeft; buffer <<= pad; bitsLeft += pad; } } int index = 0x1F & (buffer >> (bitsLeft - 5)); bitsLeft -= 5; result[count++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[index]; } } if (count < bufSize) { result[count] = '\000'; } return count; } 0707010000001E000081A40000000000000000000000016627DD810000056B000000000000000000000000000000000000002E00000000google-authenticator-libpam-1.10/src/base32.h// Base32 implementation // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Encode and decode from base32 encoding using the following alphabet: // ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 // This alphabet is documented in RFC 4648/3548 // // We allow white-space and hyphens, but all other characters are considered // invalid. // // All functions return the number of output bytes or -1 on error. If the // output buffer is too small, the result will silently be truncated. #ifndef _BASE32_H_ #define _BASE32_H_ #include <stdint.h> int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) __attribute__((visibility("hidden"))); int base32_encode(const uint8_t *data, int length, uint8_t *result, int bufSize) __attribute__((visibility("hidden"))); #endif /* _BASE32_H_ */ 0707010000001F000081A40000000000000000000000016627DD81000016CC000000000000000000000000000000000000003300000000google-authenticator-libpam-1.10/src/base32_prog.c// Base32 utility program // // Copyright 2015 TWO SIGMA OPEN SOURCE, LLC // Author: Eric Haszlakiewicz // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "config.h" #include <err.h> #include <errno.h> #include <fcntl.h> #include <getopt.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include "base32.h" /* * Convert arbitrary input from a file to base-32 encoded output on stdout. * Decode base-32 input from a file or from the command line to arbitrary * output on stdout. * * ~/google-authenticator/libpam/base32 e $(awk '{ print $2 }' < gauth.seed | xxd -r -p) */ enum modes { ENCODE_NONE = 0, ENCODE_FILE = 1, DECODE_FILE = 2, DECODE_STRING = 3, }; static void usage(const char *argv0, int exitval, char *errmsg) __attribute__((noreturn)); static void usage(const char *argv0, int exitval, char *errmsg) { FILE *f = stdout; if (errmsg) { fprintf(stderr, "ERROR: %s\n", errmsg); f = stderr; } fprintf(f, "Usage: %s -e [<file>]\n", argv0); fprintf(f, "Usage: %s -d [<file>]\n", argv0); fprintf(f, "Usage: %s -D <value>\n", argv0); fprintf(f, " Emits <value> encoded in/decoded from base-32 on stdout.\n"); fprintf(f, " When encoding, the file should contain raw binary data.\n"); fprintf(f, " If no filename is specified, it reads from stdin.\n"); fprintf(f, " All output is written to stdout.\n"); exit(exitval); } /* Write the full contents of buf to the given file descriptor, * even across multiple short writes. * If write() fails, exit with an error. */ static void full_write(int fd, uint8_t *buf, size_t remaining) { ssize_t offset, written; for (offset = 0; remaining != 0; remaining -= written, offset += written) { if ((written = write(fd, buf + offset, remaining)) < 0) { err(1, "Failed to write to stdout"); } } } int main(int argc, char *argv[]) { int c; int mode = ENCODE_NONE; while ((c = getopt(argc, argv, "edDh")) != -1) { switch (c) { case 'e': mode = ENCODE_FILE; break; case 'd': mode = DECODE_FILE; break; case 'D': mode = DECODE_STRING; break; case 'h': usage(argv[0], 0, NULL); break; default: usage(argv[0], 1, "Unknown command line argument"); break; } } if (mode == ENCODE_NONE) { usage(argv[0], 1, "A mode of operation must be chosen."); } int retval; if (mode == ENCODE_FILE || mode == DECODE_FILE) { if (argc - optind > 1) { usage(argv[0], 1, "Too many args"); } const char *binfile = (optind < argc) ? argv[optind] : "-"; const int d = strcmp(binfile, "-") == 0 ? STDIN_FILENO : open(binfile, O_RDONLY); if (d < 0) { err(1, "Failed to open %s: %s\n", binfile, strerror(errno)); } struct stat st; memset(&st, 0, sizeof(st)); if (fstat(d, &st) < 0 || st.st_size == 0) { st.st_size = 5 * 1024; // multiple of 5 to avoid internal padding // AND multiple of 8 to ensure we feed // valid data to base32_decode(). } const size_t bufsize = st.st_size + 1; if (bufsize < st.st_size) { err(1, "Integer overflow in input size"); } uint8_t *input = malloc(bufsize); if (!input) { err(1, "Failed to allocate memory"); } int amt_read; const int amt_to_read = st.st_size; errno = 0; while ((amt_read = read(d, input, amt_to_read)) > 0 || errno == EINTR) { if (errno == EINTR) { continue; } // Encoding: 8 bytes out for every 5 input, plus up to 6 padding, and nul // Decoding: up to 5 bytes out for every 8 input. const int result_avail = (mode == ENCODE_FILE) ? ((amt_read + 4) / 5 * 8 + 6 + 1) : ((amt_read + 7) / 8 * 5) ; uint8_t *result = malloc(result_avail); input[amt_read] = '\0'; if (mode == ENCODE_FILE) { retval = base32_encode(input, amt_read, result, result_avail); } else { retval = base32_decode(input, result, result_avail); } if (retval < 0) { fprintf(stderr, "%s failed. Input too long?\n", (mode == ENCODE_FILE) ? "base32_encode" : "base32_decode"); exit(1); } //printf("%s", result); full_write(STDOUT_FILENO, result, retval); fflush(stdout); free(result); } free(input); if (amt_read < 0) { err(1, "Failed to read from %s: %s\n", binfile, strerror(errno)); } if (mode == ENCODE_FILE) { printf("\n"); } } else { // mode == DECODE_STRING if (argc - optind < 1) { usage(argv[0], 1, "Not enough args"); } const char *base32_value = argv[2]; const int result_avail = strlen(base32_value) + 1; if (result_avail < strlen(base32_value)) { err(1, "Integer overflow in input size"); } uint8_t *result = malloc(result_avail); retval = base32_decode((uint8_t *)base32_value, result, result_avail); if (retval < 0) { fprintf(stderr, "base32_decode failed. Input too long?\n"); exit(1); } full_write(STDOUT_FILENO, result, retval); free(result); } return 0; } /* ---- Emacs Variables ---- * Local Variables: * c-basic-offset: 2 * indent-tabs-mode: nil * End: */ 07070100000020000081A40000000000000000000000016627DD810000755D000000000000000000000000000000000000003C00000000google-authenticator-libpam-1.10/src/google-authenticator.c// Helper program to generate a new secret for use in two-factor // authentication. // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "config.h" #include <assert.h> #include <dlfcn.h> #include <errno.h> #include <fcntl.h> #include <getopt.h> #include <pwd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <time.h> #include <unistd.h> #include "base32.h" #include "hmac.h" #include "sha1.h" #define SECRET "/.google_authenticator" #define SECRET_BITS 128 // Must be divisible by eight #define VERIFICATION_CODE_MODULUS (1000*1000) // Six digits #define SCRATCHCODES 5 // Default number of initial scratchcodes #define MAX_SCRATCHCODES 10 // Max number of initial scratchcodes #define SCRATCHCODE_LENGTH 8 // Eight digits per scratchcode #define BYTES_PER_SCRATCHCODE 4 // 32bit of randomness is enough #define BITS_PER_BASE32_CHAR 5 // Base32 expands space by 8/5 static enum { QR_UNSET=0, QR_NONE, QR_ANSI, QR_UTF8 } qr_mode = QR_UNSET; static int generateCode(const char *key, unsigned long tm) { uint8_t challenge[8]; for (int i = 8; i--; tm >>= 8) { challenge[i] = tm; } // Estimated number of bytes needed to represent the decoded secret. Because // of white-space and separators, this is an upper bound of the real number, // which we later get as a return-value from base32_decode() int secretLen = (strlen(key) + 7)/8*BITS_PER_BASE32_CHAR; // Sanity check, that our secret will fixed into a reasonably-sized static // array. if (secretLen <= 0 || secretLen > 100) { return -1; } // Decode secret from Base32 to a binary representation, and check that we // have at least one byte's worth of secret data. uint8_t secret[100]; if ((secretLen = base32_decode((const uint8_t *)key, secret, secretLen))<1) { return -1; } // Compute the HMAC_SHA1 of the secret and the challenge. uint8_t hash[SHA1_DIGEST_LENGTH]; hmac_sha1(secret, secretLen, challenge, 8, hash, SHA1_DIGEST_LENGTH); // Pick the offset where to sample our hash value for the actual verification // code. const int offset = hash[SHA1_DIGEST_LENGTH - 1] & 0xF; // Compute the truncated hash in a byte-order independent loop. unsigned int truncatedHash = 0; for (int i = 0; i < 4; ++i) { truncatedHash <<= 8; truncatedHash |= hash[offset + i]; } // Truncate to a smaller number of digits. truncatedHash &= 0x7FFFFFFF; truncatedHash %= VERIFICATION_CODE_MODULUS; return truncatedHash; } // return the user name in heap-allocated buffer. // Caller frees. static const char *getUserName(uid_t uid) { struct passwd pwbuf, *pw; char *buf; #ifdef _SC_GETPW_R_SIZE_MAX int len = sysconf(_SC_GETPW_R_SIZE_MAX); if (len <= 0) { len = 4096; } #else int len = 4096; #endif buf = malloc(len); char *user; if (getpwuid_r(uid, &pwbuf, buf, len, &pw) || !pw) { user = malloc(32); snprintf(user, 32, "%d", uid); } else { user = strdup(pw->pw_name); if (!user) { perror("malloc()"); _exit(1); } } free(buf); return user; } static const char *urlEncode(const char *s) { const size_t size = 3 * strlen(s) + 1; if (size > 10000) { // Anything "too big" is too suspect to let through. fprintf(stderr, "Error: Generated URL would be unreasonably large.\n"); exit(1); } char *ret = malloc(size); char *d = ret; do { switch (*s) { case '%': case '&': case '?': case '=': encode: snprintf(d, size-(d-ret), "%%%02X", (unsigned char)*s); d += 3; break; default: if ((*s && *s <= ' ') || *s >= '\x7F') { goto encode; } *d++ = *s; break; } } while (*s++); char* newret = realloc(ret, strlen(ret) + 1); if (newret) { ret = newret; } return ret; } static const char *getURL(const char *secret, const char *label, const int use_totp, const char *issuer) { const char *encodedLabel = urlEncode(label); char *url; const char totp = use_totp ? 't' : 'h'; if (asprintf(&url, "otpauth://%cotp/%s?secret=%s", totp, encodedLabel, secret) < 0) { fprintf(stderr, "String allocation failed, probably running out of memory.\n"); _exit(1); } if (issuer != NULL && strlen(issuer) > 0) { // Append to URL &issuer=<issuer> const char *encodedIssuer = urlEncode(issuer); char *newUrl; if (asprintf(&newUrl, "%s&issuer=%s", url, encodedIssuer) < 0) { fprintf(stderr, "String allocation failed, probably running out of memory.\n"); _exit(1); } free((void *)encodedIssuer); free(url); url = newUrl; } free((void *)encodedLabel); return url; } #define ANSI_RESET "\x1B[0m" #define ANSI_BLACKONGREY "\x1B[30;47;27m" #define ANSI_WHITE "\x1B[27m" #define ANSI_BLACK "\x1B[7m" #define UTF8_BOTH "\xE2\x96\x88" #define UTF8_TOPHALF "\xE2\x96\x80" #define UTF8_BOTTOMHALF "\xE2\x96\x84" // Display QR code visually. If not possible, return 0. static int displayQRCode(const char* url) { void *qrencode = dlopen("libqrencode.so.2", RTLD_NOW | RTLD_LOCAL); if (!qrencode) { qrencode = dlopen("libqrencode.so.3", RTLD_NOW | RTLD_LOCAL); } if (!qrencode) { qrencode = dlopen("libqrencode.so.4", RTLD_NOW | RTLD_LOCAL); } if (!qrencode) { qrencode = dlopen("libqrencode.3.dylib", RTLD_NOW | RTLD_LOCAL); } if (!qrencode) { qrencode = dlopen("libqrencode.4.dylib", RTLD_NOW | RTLD_LOCAL); } if (!qrencode) { return 0; } typedef struct { int version; int width; unsigned char *data; } QRcode; QRcode *(*QRcode_encodeString8bit)(const char *, int, int) = (QRcode *(*)(const char *, int, int)) dlsym(qrencode, "QRcode_encodeString8bit"); void (*QRcode_free)(QRcode *qrcode) = (void (*)(QRcode *))dlsym(qrencode, "QRcode_free"); if (!QRcode_encodeString8bit || !QRcode_free) { dlclose(qrencode); return 0; } QRcode *qrcode = QRcode_encodeString8bit(url, 0, 1); const char *ptr = (char *)qrcode->data; // Output QRCode using ANSI colors. Instead of black on white, we // output black on grey, as that works independently of whether the // user runs their terminal in a black on white or white on black color // scheme. // But this requires that we print a border around the entire QR Code. // Otherwise readers won't be able to recognize it. if (qr_mode != QR_UTF8) { for (int i = 0; i < 2; ++i) { printf(ANSI_BLACKONGREY); for (int x = 0; x < qrcode->width + 4; ++x) printf(" "); puts(ANSI_RESET); } for (int y = 0; y < qrcode->width; ++y) { printf(ANSI_BLACKONGREY" "); int isBlack = 0; for (int x = 0; x < qrcode->width; ++x) { if (*ptr++ & 1) { if (!isBlack) { printf(ANSI_BLACK); } isBlack = 1; } else { if (isBlack) { printf(ANSI_WHITE); } isBlack = 0; } printf(" "); } if (isBlack) { printf(ANSI_WHITE); } puts(" "ANSI_RESET); } for (int i = 0; i < 2; ++i) { printf(ANSI_BLACKONGREY); for (int x = 0; x < qrcode->width + 4; ++x) printf(" "); puts(ANSI_RESET); } } else { // Drawing the QRCode with Unicode block elements is desirable as // it makes the code much smaller, which is often easier to scan. // Unfortunately, many terminal emulators do not display these // Unicode characters properly. printf(ANSI_BLACKONGREY); for (int i = 0; i < qrcode->width + 4; ++i) { printf(" "); } puts(ANSI_RESET); for (int y = 0; y < qrcode->width; y += 2) { printf(ANSI_BLACKONGREY" "); for (int x = 0; x < qrcode->width; ++x) { const int top = qrcode->data[y*qrcode->width + x] & 1; int bottom = 0; if (y+1 < qrcode->width) { bottom = qrcode->data[(y+1)*qrcode->width + x] & 1; } if (top) { if (bottom) { printf(UTF8_BOTH); } else { printf(UTF8_TOPHALF); } } else { if (bottom) { printf(UTF8_BOTTOMHALF); } else { printf(" "); } } } puts(" "ANSI_RESET); } printf(ANSI_BLACKONGREY); for (int i = 0; i < qrcode->width + 4; ++i) { printf(" "); } puts(ANSI_RESET); } QRcode_free(qrcode); dlclose(qrencode); return 1; } // Display to the user what they need to provision their app. static void displayEnrollInfo(const char *secret, const char *label, const int use_totp, const char *issuer) { if (qr_mode == QR_NONE) { return; } const char *url = getURL(secret, label, use_totp, issuer); // Only newer systems have support for libqrencode. So instead of requiring // it at build-time we look for it at run-time. If it cannot be found, the // user can still type the code in manually or copy the URL into // their browser. if (isatty(STDOUT_FILENO)) { if (!displayQRCode(url)) { printf( "Failed to use libqrencode to show QR code visually for scanning.\n" "Consider typing the OTP secret into your app manually.\n"); } } free((char *)url); } // ask for a code. Return code, or some garbage if no number given. That's fine // because bad data also won't match code. static int ask_code(const char* msg) { char line[128]; printf("%s ", msg); fflush(stdout); line[sizeof(line)-1] = 0; if (NULL == fgets(line, sizeof line, stdin)) { if (errno == 0) { printf("\n"); } else { perror("getline()"); } exit(1); } return strtol(line, NULL, 10); } // ask y/n, and return 0 for no, 1 for yes. static int maybe(const char *msg) { printf("\n"); for (;;) { char line[128]; printf("%s (y/n) ", msg); fflush(stdout); line[sizeof(line)-1] = 0; if (NULL == fgets(line, sizeof(line), stdin)) { if (errno == 0) { printf("\n"); } else { perror("getline()"); } exit(1); } switch (line[0]) { case 'Y': case 'y': return 1; case 'N': case 'n': return 0; } } } static char *addOption(char *buf, size_t nbuf, const char *option) { assert(strlen(buf) + strlen(option) < nbuf); char *scratchCodes = strchr(buf, '\n'); assert(scratchCodes); scratchCodes++; memmove(scratchCodes + strlen(option), scratchCodes, strlen(scratchCodes) + 1); memcpy(scratchCodes, option, strlen(option)); return buf; } static char *maybeAddOption(const char *msg, char *buf, size_t nbuf, const char *option) { if (maybe(msg)) { buf = addOption(buf, nbuf, option); } return buf; } static void print_version() { puts("google-authenticator "VERSION); } static void usage(void) { print_version(); puts( "google-authenticator [<options>]\n" " -h, --help Print this message\n" " --version Print version\n" " -c, --counter-based Set up counter-based (HOTP) verification\n" " -C, --no-confirm Don't confirm code. For non-interactive setups\n" " -t, --time-based Set up time-based (TOTP) verification\n" " -d, --disallow-reuse Disallow reuse of previously used TOTP tokens\n" " -D, --allow-reuse Allow reuse of previously used TOTP tokens\n" " -f, --force Write file without first confirming with user\n" " -l, --label=<label> Override the default label in \"otpauth://\" URL\n" " -i, --issuer=<issuer> Override the default issuer in \"otpauth://\" URL\n" " -q, --quiet Quiet mode\n" " -Q, --qr-mode={NONE,ANSI,UTF8} QRCode output mode\n" " -r, --rate-limit=N Limit logins to N per every M seconds\n" " -R, --rate-time=M Limit logins to N per every M seconds\n" " -u, --no-rate-limit Disable rate-limiting\n" " -s, --secret=<file> Specify a non-standard file location\n" " -S, --step-size=S Set interval between token refreshes\n" " -w, --window-size=W Set window of concurrently valid codes\n" " -W, --minimal-window Disable window of concurrently valid codes\n" " -e, --emergency-codes=N Number of emergency codes to generate"); } int main(int argc, char *argv[]) { uint8_t buf[SECRET_BITS/8 + MAX_SCRATCHCODES*BYTES_PER_SCRATCHCODE]; static const char hotp[] = "\" HOTP_COUNTER 1\n"; static const char totp[] = "\" TOTP_AUTH\n"; static const char disallow[] = "\" DISALLOW_REUSE\n"; static const char step[] = "\" STEP_SIZE 30\n"; static const char window[] = "\" WINDOW_SIZE 17\n"; static const char ratelimit[] = "\" RATE_LIMIT 3 30\n"; char secret[(SECRET_BITS + BITS_PER_BASE32_CHAR-1)/BITS_PER_BASE32_CHAR + 1 /* newline */ + sizeof(hotp) + // hotp and totp are mutually exclusive. sizeof(disallow) + sizeof(step) + sizeof(window) + sizeof(ratelimit) + 5 + // NN MMM (total of five digits) SCRATCHCODE_LENGTH*(MAX_SCRATCHCODES + 1 /* newline */) + 1 /* NUL termination character */]; enum { ASK_MODE, HOTP_MODE, TOTP_MODE } mode = ASK_MODE; enum { ASK_REUSE, DISALLOW_REUSE, ALLOW_REUSE } reuse = ASK_REUSE; int force = 0, quiet = 0; int r_limit = 0, r_time = 0; char *secret_fn = NULL; char *label = NULL; char *issuer = NULL; int step_size = 0; int confirm = 1; int window_size = 0; int emergency_codes = -1; int idx; for (;;) { static const char optstring[] = "+hcCtdDfl:i:qQ:r:R:us:S:w:We:"; static struct option options[] = { { "help", 0, 0, 'h' }, { "version", 0, 0, 0}, { "counter-based", 0, 0, 'c' }, { "no-confirm", 0, 0, 'C' }, { "time-based", 0, 0, 't' }, { "disallow-reuse", 0, 0, 'd' }, { "allow-reuse", 0, 0, 'D' }, { "force", 0, 0, 'f' }, { "label", 1, 0, 'l' }, { "issuer", 1, 0, 'i' }, { "quiet", 0, 0, 'q' }, { "qr-mode", 1, 0, 'Q' }, { "rate-limit", 1, 0, 'r' }, { "rate-time", 1, 0, 'R' }, { "no-rate-limit", 0, 0, 'u' }, { "secret", 1, 0, 's' }, { "step-size", 1, 0, 'S' }, { "window-size", 1, 0, 'w' }, { "minimal-window", 0, 0, 'W' }, { "emergency-codes", 1, 0, 'e' }, { 0, 0, 0, 0 } }; idx = -1; const int c = getopt_long(argc, argv, optstring, options, &idx); if (c > 0) { for (int i = 0; options[i].name; i++) { if (options[i].val == c) { idx = i; break; } } } else if (c < 0) { break; } if (idx-- <= 0) { // Help (or invalid argument) err: usage(); if (idx < -1) { fprintf(stderr, "Failed to parse command line\n"); _exit(1); } exit(0); } else if (!idx--) { // --version print_version(); exit(0); } else if (!idx--) { // counter-based, -c if (mode != ASK_MODE) { fprintf(stderr, "Duplicate -c and/or -t option detected\n"); _exit(1); } if (reuse != ASK_REUSE) { reuse_err: fprintf(stderr, "Reuse of tokens is not a meaningful parameter " "in counter-based mode\n"); _exit(1); } mode = HOTP_MODE; } else if (!idx--) { // don't confirm code provisioned, -C confirm = 0; } else if (!idx--) { // time-based if (mode != ASK_MODE) { fprintf(stderr, "Duplicate -c and/or -t option detected\n"); _exit(1); } mode = TOTP_MODE; } else if (!idx--) { // disallow-reuse if (reuse != ASK_REUSE) { fprintf(stderr, "Duplicate -d and/or -D option detected\n"); _exit(1); } if (mode == HOTP_MODE) { goto reuse_err; } reuse = DISALLOW_REUSE; } else if (!idx--) { // allow-reuse if (reuse != ASK_REUSE) { fprintf(stderr, "Duplicate -d and/or -D option detected\n"); _exit(1); } if (mode == HOTP_MODE) { goto reuse_err; } reuse = ALLOW_REUSE; } else if (!idx--) { // force if (force) { fprintf(stderr, "Duplicate -f option detected\n"); _exit(1); } force = 1; } else if (!idx--) { // label if (label) { fprintf(stderr, "Duplicate -l option detected\n"); _exit(1); } label = strdup(optarg); } else if (!idx--) { // issuer if (issuer) { fprintf(stderr, "Duplicate -i option detected\n"); _exit(1); } issuer = strdup(optarg); } else if (!idx--) { // quiet if (quiet) { fprintf(stderr, "Duplicate -q option detected\n"); _exit(1); } quiet = 1; } else if (!idx--) { // qr-mode if (qr_mode != QR_UNSET) { fprintf(stderr, "Duplicate -Q option detected\n"); _exit(1); } if (!strcasecmp(optarg, "none")) { qr_mode = QR_NONE; } else if (!strcasecmp(optarg, "ansi")) { qr_mode = QR_ANSI; } else if (!strcasecmp(optarg, "utf8")) { qr_mode = QR_UTF8; } else { fprintf(stderr, "Invalid qr-mode \"%s\"\n", optarg); _exit(1); } } else if (!idx--) { // rate-limit if (r_limit > 0) { fprintf(stderr, "Duplicate -r option detected\n"); _exit(1); } else if (r_limit < 0) { fprintf(stderr, "-u is mutually exclusive with -r\n"); _exit(1); } char *endptr; errno = 0; const long l = strtol(optarg, &endptr, 10); if (errno || endptr == optarg || *endptr || l < 1 || l > 10) { fprintf(stderr, "-r requires an argument in the range 1..10\n"); _exit(1); } r_limit = (int)l; } else if (!idx--) { // rate-time if (r_time > 0) { fprintf(stderr, "Duplicate -R option detected\n"); _exit(1); } else if (r_time < 0) { fprintf(stderr, "-u is mutually exclusive with -R\n"); _exit(1); } char *endptr; errno = 0; const long l = strtol(optarg, &endptr, 10); if (errno || endptr == optarg || *endptr || l < 15 || l > 600) { fprintf(stderr, "-R requires an argument in the range 15..600\n"); _exit(1); } r_time = (int)l; } else if (!idx--) { // no-rate-limit if (r_limit > 0 || r_time > 0) { fprintf(stderr, "-u is mutually exclusive with -r/-R\n"); _exit(1); } if (r_limit < 0) { fprintf(stderr, "Duplicate -u option detected\n"); _exit(1); } r_limit = r_time = -1; } else if (!idx--) { // secret if (secret_fn) { fprintf(stderr, "Duplicate -s option detected\n"); _exit(1); } if (!*optarg) { fprintf(stderr, "-s must be followed by a filename\n"); _exit(1); } secret_fn = strdup(optarg); if (!secret_fn) { perror("malloc()"); _exit(1); } } else if (!idx--) { // step-size if (step_size) { fprintf(stderr, "Duplicate -S option detected\n"); _exit(1); } char *endptr; errno = 0; const long l = strtol(optarg, &endptr, 10); if (errno || endptr == optarg || *endptr || l < 1 || l > 60) { fprintf(stderr, "-S requires an argument in the range 1..60\n"); _exit(1); } step_size = (int)l; } else if (!idx--) { // window-size if (window_size) { fprintf(stderr, "Duplicate -w/-W option detected\n"); _exit(1); } char *endptr; errno = 0; const long l = strtol(optarg, &endptr, 10); if (errno || endptr == optarg || *endptr || l < 1 || l > 21) { fprintf(stderr, "-w requires an argument in the range 1..21\n"); _exit(1); } window_size = (int)l; } else if (!idx--) { // minimal-window if (window_size) { fprintf(stderr, "Duplicate -w/-W option detected\n"); _exit(1); } window_size = -1; } else if (!idx--) { // emergency-codes if (emergency_codes >= 0) { fprintf(stderr, "Duplicate -e option detected\n"); _exit(1); } char *endptr; errno = 0; long l = strtol(optarg, &endptr, 10); if (errno || endptr == optarg || *endptr || l < 0 || l > MAX_SCRATCHCODES) { fprintf(stderr, "-e requires an argument in the range 0..%d\n", MAX_SCRATCHCODES); _exit(1); } emergency_codes = (int)l; } else { fprintf(stderr, "Error\n"); _exit(1); } } idx = -1; if (optind != argc) { goto err; } if (reuse != ASK_REUSE && mode != TOTP_MODE) { fprintf(stderr, "Must select time-based mode, when using -d or -D\n"); _exit(1); } if ((r_time && !r_limit) || (!r_time && r_limit)) { fprintf(stderr, "Must set -r when setting -R, and vice versa\n"); _exit(1); } if (emergency_codes < 0) { emergency_codes = SCRATCHCODES; } if (!label) { const uid_t uid = getuid(); const char *user = getUserName(uid); char hostname[128] = { 0 }; if (gethostname(hostname, sizeof(hostname)-1)) { strcpy(hostname, "unix"); } label = strcat(strcat(strcpy(malloc(strlen(user) + strlen(hostname) + 2), user), "@"), hostname); free((char *)user); } if (!issuer) { char hostname[128] = { 0 }; if (gethostname(hostname, sizeof(hostname)-1)) { strcpy(hostname, "unix"); } issuer = strdup(hostname); } // Not const because 'fd' is reused. TODO. int fd = open("/dev/urandom", O_RDONLY); if (fd < 0) { perror("Failed to open \"/dev/urandom\""); return 1; } if (read(fd, buf, sizeof(buf)) != sizeof(buf)) { urandom_failure: perror("Failed to read from \"/dev/urandom\""); return 1; } base32_encode(buf, SECRET_BITS/8, (uint8_t *)secret, sizeof(secret)); int use_totp; if (mode == ASK_MODE) { use_totp = maybe("Do you want authentication tokens to be time-based"); } else { use_totp = mode == TOTP_MODE; } if (!quiet) { displayEnrollInfo(secret, label, use_totp, issuer); printf("Your new secret key is: %s\n", secret); // Confirm code. if (confirm && use_totp) { for (;;) { const int test_code = ask_code("Enter code from app (-1 to skip):"); if (test_code < 0) { printf("Code confirmation skipped\n"); break; } const unsigned long tm = time(NULL)/(step_size ? step_size : 30); const int correct_code = generateCode(secret, tm); if (test_code == correct_code) { printf("Code confirmed\n"); break; } printf("Code incorrect (correct code %06d). Try again.\n", correct_code); } } else { const unsigned long tm = 1; printf("Your verification code for code %lu is %06d\n", tm, generateCode(secret, tm)); } printf("Your emergency scratch codes are:\n"); } free(label); free(issuer); strcat(secret, "\n"); if (use_totp) { strcat(secret, totp); } else { strcat(secret, hotp); } for (int i = 0; i < emergency_codes; ++i) { new_scratch_code:; int scratch = 0; for (int j = 0; j < BYTES_PER_SCRATCHCODE; ++j) { scratch = 256*scratch + buf[SECRET_BITS/8 + BYTES_PER_SCRATCHCODE*i + j]; } int modulus = 1; for (int j = 0; j < SCRATCHCODE_LENGTH; j++) { modulus *= 10; } scratch = (scratch & 0x7FFFFFFF) % modulus; if (scratch < modulus/10) { // Make sure that scratch codes are always exactly eight digits. If they // start with a sequence of zeros, just generate a new scratch code. if (read(fd, buf + (SECRET_BITS/8 + BYTES_PER_SCRATCHCODE*i), BYTES_PER_SCRATCHCODE) != BYTES_PER_SCRATCHCODE) { goto urandom_failure; } goto new_scratch_code; } if (!quiet) { printf(" %08d\n", scratch); } snprintf(strrchr(secret, '\000'), sizeof(secret) - strlen(secret), "%08d\n", scratch); } close(fd); if (!secret_fn) { const char *home = getenv("HOME"); if (!home || *home != '/') { fprintf(stderr, "Cannot determine home directory\n"); return 1; } secret_fn = malloc(strlen(home) + strlen(SECRET) + 1); if (!secret_fn) { perror("malloc()"); _exit(1); } strcat(strcpy(secret_fn, home), SECRET); } if (!force) { char s[1024]; snprintf(s, sizeof s, "Do you want me to update your \"%s\" file?", secret_fn); if (!maybe(s)) { exit(0); } } const int size = strlen(secret_fn) + 3; char* tmp_fn = malloc(size); if (!tmp_fn) { perror("malloc()"); _exit(1); } snprintf(tmp_fn, size, "%s~", secret_fn); // Add optional flags. if (use_totp) { if (reuse == ASK_REUSE) { maybeAddOption("Do you want to disallow multiple uses of the same " "authentication\ntoken? This restricts you to one login " "about every 30s, but it increases\nyour chances to " "notice or even prevent man-in-the-middle attacks", secret, sizeof(secret), disallow); } else if (reuse == DISALLOW_REUSE) { addOption(secret, sizeof(secret), disallow); } if (step_size) { char s[80]; snprintf(s, sizeof s, "\" STEP_SIZE %d\n", step_size); addOption(secret, sizeof(secret), s); } if (!window_size) { maybeAddOption("By default, a new token is generated every 30 seconds by" " the mobile app.\nIn order to compensate for possible" " time-skew between the client and the server,\nwe allow" " an extra token before and after the current time. This" " allows for a\ntime skew of up to 30 seconds between" " authentication server and client. If you\nexperience" " problems with poor time synchronization, you can" " increase the window\nfrom its default size of 3" " permitted codes (one previous code, the current\ncode," " the next code) to 17 permitted codes (the 8 previous" " codes, the current\ncode, and the 8 next codes)." " This will permit for a time skew of up to 4 minutes" "\nbetween client and server." "\nDo you want to do so?", secret, sizeof(secret), window); } else { char s[80]; // TODO: Should 3 really be the minimal window size for TOTP? // If so, the code should not allow -w=1 here. snprintf(s, sizeof s, "\" WINDOW_SIZE %d\n", window_size > 0 ? window_size : 3); addOption(secret, sizeof(secret), s); } } else { // Counter based. if (!window_size) { maybeAddOption("By default, three tokens are valid at any one time. " "This accounts for\ngenerated-but-not-used tokens and " "failed login attempts. In order to\ndecrease the " "likelihood of synchronization problems, this window " "can be\nincreased from its default size of 3 to 17. Do " "you want to do so?", secret, sizeof(secret), window); } else { char s[80]; snprintf(s, sizeof s, "\" WINDOW_SIZE %d\n", window_size > 0 ? window_size : 1); addOption(secret, sizeof(secret), s); } } if (!r_limit && !r_time) { maybeAddOption("If the computer that you are logging into isn't hardened " "against brute-force\nlogin attempts, you can enable " "rate-limiting for the authentication module.\nBy default, " "this limits attackers to no more than 3 login attempts " "every 30s.\nDo you want to enable rate-limiting?", secret, sizeof(secret), ratelimit); } else if (r_limit > 0 && r_time > 0) { char s[80]; snprintf(s, sizeof s, "\" RATE_LIMIT %d %d\n", r_limit, r_time); addOption(secret, sizeof(secret), s); } fd = open(tmp_fn, O_WRONLY|O_EXCL|O_CREAT|O_NOFOLLOW|O_TRUNC, 0400); if (fd < 0) { fprintf(stderr, "Failed to create \"%s\" (%s)", secret_fn, strerror(errno)); goto errout; } if (write(fd, secret, strlen(secret)) != (ssize_t)strlen(secret) || rename(tmp_fn, secret_fn)) { perror("Failed to write new secret"); unlink(secret_fn); goto errout; } free(tmp_fn); free(secret_fn); close(fd); return 0; errout: if (fd > 0) { close(fd); } free(secret_fn); free(tmp_fn); return 1; } /* ---- Emacs Variables ---- * Local Variables: * c-basic-offset: 2 * indent-tabs-mode: nil * End: */ 07070100000021000081A40000000000000000000000016627DD81000009FE000000000000000000000000000000000000002C00000000google-authenticator-libpam-1.10/src/hmac.c// HMAC_SHA1 implementation // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include <string.h> #include "hmac.h" #include "sha1.h" #include "util.h" void hmac_sha1(const uint8_t *key, int keyLength, const uint8_t *data, int dataLength, uint8_t *result, int resultLength) { SHA1_INFO ctx; uint8_t hashed_key[SHA1_DIGEST_LENGTH]; if (keyLength > 64) { // The key can be no bigger than 64 bytes. If it is, we'll hash it down to // 20 bytes. sha1_init(&ctx); sha1_update(&ctx, key, keyLength); sha1_final(&ctx, hashed_key); key = hashed_key; keyLength = SHA1_DIGEST_LENGTH; } // The key for the inner digest is derived from our key, by padding the key // the full length of 64 bytes, and then XOR'ing each byte with 0x36. uint8_t tmp_key[64]; for (int i = 0; i < keyLength; ++i) { tmp_key[i] = key[i] ^ 0x36; } if (keyLength < 64) { memset(tmp_key + keyLength, 0x36, 64 - keyLength); } // Compute inner digest sha1_init(&ctx); sha1_update(&ctx, tmp_key, 64); sha1_update(&ctx, data, dataLength); uint8_t sha[SHA1_DIGEST_LENGTH]; sha1_final(&ctx, sha); // The key for the outer digest is derived from our key, by padding the key // the full length of 64 bytes, and then XOR'ing each byte with 0x5C. for (int i = 0; i < keyLength; ++i) { tmp_key[i] = key[i] ^ 0x5C; } memset(tmp_key + keyLength, 0x5C, 64 - keyLength); // Compute outer digest sha1_init(&ctx); sha1_update(&ctx, tmp_key, 64); sha1_update(&ctx, sha, SHA1_DIGEST_LENGTH); sha1_final(&ctx, sha); // Copy result to output buffer and truncate or pad as necessary memset(result, 0, resultLength); if (resultLength > SHA1_DIGEST_LENGTH) { resultLength = SHA1_DIGEST_LENGTH; } memcpy(result, sha, resultLength); // Zero out all internal data structures explicit_bzero(hashed_key, sizeof(hashed_key)); explicit_bzero(sha, sizeof(sha)); explicit_bzero(tmp_key, sizeof(tmp_key)); } 07070100000022000081A40000000000000000000000016627DD8100000397000000000000000000000000000000000000002C00000000google-authenticator-libpam-1.10/src/hmac.h// HMAC_SHA1 implementation // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef _HMAC_H_ #define _HMAC_H_ #include <stdint.h> void hmac_sha1(const uint8_t *key, int keyLength, const uint8_t *data, int dataLength, uint8_t *result, int resultLength) __attribute__((visibility("hidden"))); #endif /* _HMAC_H_ */ 07070100000023000081A40000000000000000000000016627DD810001014C000000000000000000000000000000000000004000000000google-authenticator-libpam-1.10/src/pam_google_authenticator.c// PAM module for two-factor authentication. // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "config.h" #include <errno.h> #include <fcntl.h> #include <limits.h> #include <pwd.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <syslog.h> #include <time.h> #include <unistd.h> #ifdef HAVE_SYS_FSUID_H // We much rather prefer to use setfsuid(), but this function is unfortunately // not available on all systems. #include <sys/fsuid.h> #endif #ifndef PAM_EXTERN #define PAM_EXTERN #endif #if !defined(LOG_AUTHPRIV) && defined(LOG_AUTH) #define LOG_AUTHPRIV LOG_AUTH #endif #define PAM_SM_AUTH #include <security/pam_appl.h> #include <security/pam_modules.h> #include "base32.h" #include "hmac.h" #include "sha1.h" #include "util.h" // Module name shortened to work with rsyslog. // See https://github.com/google/google-authenticator-libpam/issues/172 #define MODULE_NAME "pam_google_auth" #define SECRET "~/.google_authenticator" #define CODE_PROMPT "Verification code: " #define PWCODE_PROMPT "Password & verification code: " typedef struct Params { const char *secret_filename_spec; const char *authtok_prompt; enum { NULLERR=0, NULLOK, SECRETNOTFOUND } nullok; int noskewadj; int echocode; int fixed_uid; int no_increment_hotp; uid_t uid; enum { PROMPT = 0, TRY_FIRST_PASS, USE_FIRST_PASS } pass_mode; int forward_pass; int debug; int no_strict_owner; int allowed_perm; time_t grace_period; int allow_readonly; } Params; static char oom; static const char* nobody = "nobody"; #if defined(DEMO) || defined(TESTING) static char* error_msg = NULL; const char *get_error_msg(void) __attribute__((visibility("default"))); const char *get_error_msg(void) { if (!error_msg) { return ""; } return error_msg; } #endif static void log_message(int priority, pam_handle_t *pamh, const char *format, ...) { char *service = NULL; if (pamh) pam_get_item(pamh, PAM_SERVICE, (void *)&service); if (!service) service = ""; char logname[80]; snprintf(logname, sizeof(logname), "%s(" MODULE_NAME ")", service); va_list args; va_start(args, format); #if !defined(DEMO) && !defined(TESTING) openlog(logname, LOG_CONS | LOG_PID, LOG_AUTHPRIV); vsyslog(priority, format, args); closelog(); #else if (!error_msg) { error_msg = strdup(""); } { char buf[1000]; vsnprintf(buf, sizeof buf, format, args); const int newlen = strlen(error_msg) + 1 + strlen(buf) + 1; char* n = malloc(newlen); if (n) { snprintf(n, newlen, "%s%s%s", error_msg, strlen(error_msg)?"\n":"",buf); free(error_msg); error_msg = n; } else { fprintf(stderr, "Failed to malloc %d bytes for log data.\n", newlen); } } #endif va_end(args); if (priority == LOG_EMERG) { // Something really bad happened. There is no way we can proceed safely. _exit(1); } } static int converse(pam_handle_t *pamh, int nargs, PAM_CONST struct pam_message **message, struct pam_response **response) { struct pam_conv *conv; int retval = pam_get_item(pamh, PAM_CONV, (void *)&conv); if (retval != PAM_SUCCESS) { return retval; } return conv->conv(nargs, message, response, conv->appdata_ptr); } static const char *get_user_name(pam_handle_t *pamh, const Params *params) { // Obtain the user's name const char *username; if (pam_get_user(pamh, &username, NULL) != PAM_SUCCESS || !username || !*username) { log_message(LOG_ERR, pamh, "pam_get_user() failed to get a user name" " when checking verification code"); return NULL; } if (params->debug) { log_message(LOG_INFO, pamh, "debug: start of google_authenticator for \"%s\"", username); } return username; } /* * Return rhost as a string. Return value must not be free()ed. * Returns NULL if PAM_RHOST is not known. */ static const char * get_rhost(pam_handle_t *pamh, const Params *params) { // Get the remote host PAM_CONST void *rhost; if (pam_get_item(pamh, PAM_RHOST, &rhost) != PAM_SUCCESS) { log_message(LOG_ERR, pamh, "pam_get_rhost() failed to get the remote host"); return NULL; } if (params->debug) { log_message(LOG_INFO, pamh, "debug: google_authenticator for host \"%s\"", rhost); } return (const char *)rhost; } static size_t getpwnam_buf_max_size() { #ifdef _SC_GETPW_R_SIZE_MAX const ssize_t len = sysconf(_SC_GETPW_R_SIZE_MAX); if (len <= 0) { return 4096; } return len; #else return 4096; #endif } static char *get_secret_filename(pam_handle_t *pamh, const Params *params, const char *username, uid_t *uid) { if (!username) { return NULL; } // Check whether the administrator decided to override the default location // for the secret file. const char *spec = params->secret_filename_spec ? params->secret_filename_spec : SECRET; // Obtain the user's id and home directory // NOTE: These variables need to be here because of their lifetimes. struct passwd *pw = NULL; // Used struct passwd pwbuf; // Here because `pw` points into it. char *buf = NULL; // Here because `pw` members use it. char *secret_filename = NULL; // Here because goto jumps. if (!params->fixed_uid) { const int len = getpwnam_buf_max_size(); buf = malloc(len); *uid = -1; if (buf == NULL) { log_message(LOG_ERR, pamh, "Short (%d) mem allocation failed", len); goto errout; } const int rc = getpwnam_r(username, &pwbuf, buf, len, &pw); if (rc) { log_message(LOG_ERR, pamh, "getpwnam_r(\"%s\")!=0: %d", username, rc); goto errout; } if (!pw) { log_message(LOG_ERR, pamh, "user(\"%s\") not found", username); goto errout; } if (!pw->pw_dir) { log_message(LOG_ERR, pamh, "user(\"%s\") has no home dir", username); goto errout; } if (*pw->pw_dir != '/') { log_message(LOG_ERR, pamh, "User \"%s\" home dir not absolute", username); goto errout; } } // Expand filename specification to an actual filename. if ((secret_filename = strdup(spec)) == NULL) { log_message(LOG_ERR, pamh, "Short (%d) mem allocation failed", strlen(spec)); goto errout; } int allow_tilde = 1; for (int offset = 0; secret_filename[offset];) { char *cur = secret_filename + offset; char *var = NULL; size_t var_len = 0; const char *subst = NULL; if (allow_tilde && *cur == '~') { var_len = 1; if (!pw) { log_message(LOG_ERR, pamh, "Home dir in 'secret' not implemented when 'user' set"); goto errout; } subst = pw->pw_dir; var = cur; } else if (secret_filename[offset] == '$') { if (!memcmp(cur, "${HOME}", 7)) { var_len = 7; if (!pw) { log_message(LOG_ERR, pamh, "Home dir in 'secret' not implemented when 'user' set"); goto errout; } subst = pw->pw_dir; var = cur; } else if (!memcmp(cur, "${USER}", 7)) { var_len = 7; subst = username; var = cur; } } if (var) { const size_t subst_len = strlen(subst); if (subst_len > 1000000) { log_message(LOG_ERR, pamh, "Unexpectedly large path name: %d", subst_len); goto errout; } const int varidx = var - secret_filename; char *resized = realloc(secret_filename, strlen(secret_filename) + subst_len + 1); if (!resized) { log_message(LOG_ERR, pamh, "Short mem allocation failed"); goto errout; } var = resized + varidx; secret_filename = resized; memmove(var + subst_len, var + var_len, strlen(var + var_len) + 1); memmove(var, subst, subst_len); offset = var + subst_len - resized; allow_tilde = 0; } else { allow_tilde = *cur == '/'; ++offset; } } *uid = params->fixed_uid ? params->uid : pw->pw_uid; free(buf); return secret_filename; errout: free(secret_filename); free(buf); return NULL; } static int setuser(int uid) { #ifdef HAVE_SETFSUID // The semantics for setfsuid() are a little unusual. On success, the // previous user id is returned. On failure, the current user id is returned. int old_uid = setfsuid(uid); if (uid != setfsuid(uid)) { setfsuid(old_uid); return -1; } #else #ifdef linux #error "Linux should have setfsuid(). Refusing to build." #endif int old_uid = geteuid(); if (old_uid != uid && seteuid(uid)) { return -1; } #endif return old_uid; } static int setgroup(int gid) { #ifdef HAVE_SETFSGID // The semantics of setfsgid() are a little unusual. On success, the // previous group id is returned. On failure, the current groupd id is // returned. int old_gid = setfsgid(gid); if (gid != setfsgid(gid)) { setfsgid(old_gid); return -1; } #else int old_gid = getegid(); if (old_gid != gid && setegid(gid)) { return -1; } #endif return old_gid; } // Drop privileges and return 0 on success. static int drop_privileges(pam_handle_t *pamh, const char *username, int uid, int *old_uid, int *old_gid) { // Try to become the new user. This might be necessary for NFS mounted home // directories. // First, look up the user's default group #ifdef _SC_GETPW_R_SIZE_MAX int len = sysconf(_SC_GETPW_R_SIZE_MAX); if (len <= 0) { len = 4096; } #else int len = 4096; #endif char *buf = malloc(len); if (!buf) { log_message(LOG_ERR, pamh, "Out of memory"); return -1; } struct passwd pwbuf, *pw; if (getpwuid_r(uid, &pwbuf, buf, len, &pw) || !pw) { log_message(LOG_ERR, pamh, "Cannot look up user id %d", uid); free(buf); return -1; } gid_t gid = pw->pw_gid; free(buf); int gid_o = setgroup(gid); int uid_o = setuser(uid); if (uid_o < 0) { if (gid_o >= 0) { if (setgroup(gid_o) < 0 || setgroup(gid_o) != gid_o) { // Inform the caller that we were unsuccessful in resetting the group. *old_gid = gid_o; } } log_message(LOG_ERR, pamh, "Failed to change user id to \"%s\"", username); return -1; } if (gid_o < 0 && (gid_o = setgroup(gid)) < 0) { // In most typical use cases, the PAM module will end up being called // while uid=0. This allows the module to change to an arbitrary group // prior to changing the uid. But there are many ways that PAM modules // can be invoked and in some scenarios this might not work. So, we also // try changing the group _after_ changing the uid. It might just work. if (setuser(uid_o) < 0 || setuser(uid_o) != uid_o) { // Inform the caller that we were unsuccessful in resetting the uid. *old_uid = uid_o; } log_message(LOG_ERR, pamh, "Failed to change group id for user \"%s\" to %d", username, (int)gid); return -1; } *old_uid = uid_o; *old_gid = gid_o; return 0; } // open secret file, return fd on success, or <0 on error. static int open_secret_file(pam_handle_t *pamh, const char *secret_filename, struct Params *params, const char *username, int uid, struct stat *orig_stat) { // Try to open "~/.google_authenticator" const int fd = open(secret_filename, O_RDONLY); if (fd < 0 || fstat(fd, orig_stat) < 0) { if (params->nullok != NULLERR && errno == ENOENT) { // The user doesn't have a state file, but the administrator said // that this is OK. We still return an error from open_secret_file(), // but we remember that this was the result of a missing state file. params->nullok = SECRETNOTFOUND; } else { log_message(LOG_ERR, pamh, "Failed to read \"%s\" for \"%s\": %s", secret_filename, username, strerror(errno)); } error: if (fd >= 0) { close(fd); } return -1; } if (params->debug) { log_message(LOG_INFO, pamh, "debug: Secret file permissions are %04o." " Allowed permissions are %04o", orig_stat->st_mode & 03777, params->allowed_perm); } // Check permissions on "~/.google_authenticator". if (!S_ISREG(orig_stat->st_mode)) { log_message(LOG_ERR, pamh, "Secret file \"%s\" is not a regular file", secret_filename); goto error; } if (orig_stat->st_mode & 03777 & ~params->allowed_perm) { log_message(LOG_ERR, pamh, "Secret file \"%s\" permissions (%04o)" " are more permissive than %04o", secret_filename, orig_stat->st_mode & 03777, params->allowed_perm); goto error; } if (!params->no_strict_owner && (orig_stat->st_uid != (uid_t)uid)) { char buf[80]; if (params->fixed_uid) { snprintf(buf, sizeof buf, "user id %d", params->uid); username = buf; } log_message(LOG_ERR, pamh, "Secret file \"%s\" must be owned by \"%s\"", secret_filename, username); goto error; } // Sanity check for file length if (orig_stat->st_size < 1 || orig_stat->st_size > 64*1024) { log_message(LOG_ERR, pamh, "Invalid file size for \"%s\"", secret_filename); goto error; } return fd; } // Read secret file contents. // If there's an error the file is closed, NULL is returned, and errno set. static char *read_file_contents(pam_handle_t *pamh, const Params *params, const char *secret_filename, int *fd, off_t filesize) { // Arbitrary limit to prevent integer overflow. if (filesize > 1000000) { close(*fd); errno = E2BIG; return NULL; } // Read file contents char *buf = malloc(filesize + 1); if (!buf) { log_message(LOG_ERR, pamh, "Failed to malloc %d+1", filesize); goto out; } if (filesize != read(*fd, buf, filesize)) { log_message(LOG_ERR, pamh, "Could not read \"%s\"", secret_filename); goto out; } close(*fd); *fd = -1; // The rest of the code assumes that there are no NUL bytes in the file. if (memchr(buf, 0, filesize)) { log_message(LOG_ERR, pamh, "Invalid file contents in \"%s\"", secret_filename); goto out; } // Terminate the buffer with a NUL byte. buf[filesize] = '\000'; if(params->debug) { log_message(LOG_INFO, pamh, "debug: \"%s\" read", secret_filename); } return buf; out: // If we have any data, erase it. if (buf) { explicit_bzero(buf, filesize); } free(buf); if (*fd >= 0) { close(*fd); *fd = -1; } return NULL; } static int is_totp(const char *buf) { return !!strstr(buf, "\" TOTP_AUTH"); } // Wrap write() making sure that partial writes don't break everything. // Return 0 on success, errno otherwise. static int full_write(int fd, const char* buf, size_t len) { const char* p = buf; int errors = 0; for (;;) { const ssize_t left = len - (p - buf); const ssize_t rc = write(fd, p, left); if (rc == left) { return 0; } if (rc < 0) { switch (errno) { case EAGAIN: case EINTR: if (errors++ < 3) { continue; } } return errno; } p += rc; } } // Safely overwrite the old secret file. // Return 0 on success, errno otherwise. static int write_file_contents(pam_handle_t *pamh, const Params *params, const char *secret_filename, struct stat *orig_stat, const char *buf) { int err = 0; int fd = -1; const size_t fnlength = strlen(secret_filename) + 1 + 6 + 1; char *tmp_filename = malloc(fnlength); if (tmp_filename == NULL) { err = errno; goto cleanup; } if (fnlength - 1 != snprintf(tmp_filename, fnlength, "%s~XXXXXX", secret_filename)) { err = ERANGE; goto cleanup; } const mode_t old_mask = umask(077); fd = mkstemp(tmp_filename); umask(old_mask); if (fd < 0) { err = errno; log_message(LOG_ERR, pamh, "Failed to create tempfile \"%s\": %s", tmp_filename, strerror(err)); // Couldn't open file; don't try to delete it later. free(tmp_filename); tmp_filename = NULL; goto cleanup; } if (fchmod(fd, 0400)) { err = errno; goto cleanup; } // Make sure the secret file is still the same. This prevents attackers // from opening a lot of pending sessions and then reusing the same // scratch code multiple times. // // (except for the brief race condition between this stat and the // `rename` below) { struct stat sb; if (stat(secret_filename, &sb) != 0) { err = errno; log_message(LOG_ERR, pamh, "stat(): %s", strerror(err)); goto cleanup; } if (sb.st_ino != orig_stat->st_ino || sb.st_size != orig_stat->st_size || sb.st_mtime != orig_stat->st_mtime) { err = EAGAIN; log_message(LOG_ERR, pamh, "Secret file \"%s\" changed while trying to use " "scratch code\n", secret_filename); goto cleanup; } } // Write the new file contents. if ((err = full_write(fd, buf, strlen(buf)))) { log_message(LOG_ERR, pamh, "write(): %s", strerror(err)); goto cleanup; } if (fsync(fd)) { err = errno; log_message(LOG_ERR, pamh, "fsync(): %s", strerror(err)); goto cleanup; } if (close(fd)) { err = errno; log_message(LOG_ERR, pamh, "close(): %s", strerror(err)); goto cleanup; } fd = -1; // Prevent double-close. // Double-check that the file size is correct. { struct stat st; if (stat(tmp_filename, &st)) { err = errno; log_message(LOG_ERR, pamh, "stat(%s): %s", tmp_filename, strerror(err)); goto cleanup; } const off_t want = strlen(buf); if (st.st_size == 0 || (want != st.st_size)) { err = EAGAIN; log_message(LOG_ERR, pamh, "temp file size %d. Should be non-zero and %d", st.st_size, want); goto cleanup; } } if (rename(tmp_filename, secret_filename) != 0) { err = errno; log_message(LOG_ERR, pamh, "rename(): %s", strerror(err)); goto cleanup; } free(tmp_filename); tmp_filename = NULL; // Prevent unlink & double-free. if (params->debug) { log_message(LOG_INFO, pamh, "debug: \"%s\" written", secret_filename); } cleanup: if (fd >= 0) { close(fd); } if (tmp_filename) { if (unlink(tmp_filename)) { log_message(LOG_ERR, pamh, "Failed to delete tempfile \"%s\": %s", tmp_filename, strerror(errno)); } } free(tmp_filename); if (err) { log_message(LOG_ERR, pamh, "Failed to update secret file \"%s\": %s", secret_filename, strerror(err)); return err; } return 0; } // given secret file content (buf), extract the secret and base32 decode it. // // Return pointer to `malloc()`'d secret on success (caller frees), // NULL on error. Length of secret stored in *secretLen. static uint8_t *get_shared_secret(pam_handle_t *pamh, const Params *params, const char *secret_filename, const char *buf, int *secretLen) { if (!buf) { return NULL; } // Decode secret key const int base32Len = strcspn(buf, "\n"); // Arbitrary limit to prevent integer overflow. if (base32Len > 100000) { return NULL; } *secretLen = (base32Len*5 + 7)/8; uint8_t *secret = malloc(base32Len + 1); if (secret == NULL) { *secretLen = 0; return NULL; } memcpy(secret, buf, base32Len); secret[base32Len] = '\000'; if ((*secretLen = base32_decode(secret, secret, base32Len)) < 1) { log_message(LOG_ERR, pamh, "Could not find a valid BASE32 encoded secret in \"%s\"", secret_filename); explicit_bzero(secret, base32Len); free(secret); return NULL; } memset(secret + *secretLen, 0, base32Len + 1 - *secretLen); if(params->debug) { log_message(LOG_INFO, pamh, "debug: shared secret in \"%s\" processed", secret_filename); } return secret; } #ifdef TESTING static time_t current_time; void set_time(time_t t) __attribute__((visibility("default"))); void set_time(time_t t) { current_time = t; } static time_t get_time(void) { return current_time; } #else static time_t get_time(void) { return time(NULL); } #endif static int comparator(const void *a, const void *b) { return *(unsigned int *)a - *(unsigned int *)b; } static char *get_cfg_value(pam_handle_t *pamh, const char *key, const char *buf) { const size_t key_len = strlen(key); for (const char *line = buf; *line; ) { const char *ptr; if (line[0] == '"' && line[1] == ' ' && !strncmp(line+2, key, key_len) && (!*(ptr = line+2+key_len) || *ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n')) { ptr += strspn(ptr, " \t"); size_t val_len = strcspn(ptr, "\r\n"); char *val = malloc(val_len + 1); if (!val) { log_message(LOG_ERR, pamh, "Out of memory"); return &oom; } else { memcpy(val, ptr, val_len); val[val_len] = '\000'; return val; } } else { line += strcspn(line, "\r\n"); line += strspn(line, "\r\n"); } } return NULL; } static int set_cfg_value(pam_handle_t *pamh, const char *key, const char *val, char **buf) { const size_t key_len = strlen(key); char *start = NULL; char *stop = NULL; // Find an existing line, if any. for (char *line = *buf; *line; ) { char *ptr; if (line[0] == '"' && line[1] == ' ' && !strncmp(line+2, key, key_len) && (!*(ptr = line+2+key_len) || *ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n')) { start = line; stop = start + strcspn(start, "\r\n"); stop += strspn(stop, "\r\n"); break; } else { line += strcspn(line, "\r\n"); line += strspn(line, "\r\n"); } } // If no existing line, insert immediately after the first line. if (!start) { start = *buf + strcspn(*buf, "\r\n"); start += strspn(start, "\r\n"); stop = start; } // Replace [start..stop] with the new contents. const size_t val_len = strlen(val); const size_t total_len = key_len + val_len + 4; if (total_len <= stop - start) { // We are decreasing out space requirements. Shrink the buffer and pad with // NUL characters. const size_t tail_len = strlen(stop); memmove(start + total_len, stop, tail_len + 1); memset(start + total_len + tail_len, 0, stop - start - total_len + 1); } else { // Must resize existing buffer. We cannot call realloc(), as it could // leave parts of the buffer content in unused parts of the heap. const size_t buf_len = strlen(*buf); const size_t tail_len = buf_len - (stop - *buf); char *resized = malloc(buf_len - (stop - start) + total_len + 1); if (!resized) { log_message(LOG_ERR, pamh, "Out of memory"); return -1; } memcpy(resized, *buf, start - *buf); memcpy(resized + (start - *buf) + total_len, stop, tail_len + 1); memset(*buf, 0, buf_len); free(*buf); start = start - *buf + resized; *buf = resized; } // Fill in new contents. start[0] = '"'; start[1] = ' '; memcpy(start + 2, key, key_len); start[2+key_len] = ' '; memcpy(start+3+key_len, val, val_len); start[3+key_len+val_len] = '\n'; // Check if there are any other occurrences of "value". If so, delete them. for (char *line = start + 4 + key_len + val_len; *line; ) { char *ptr; if (line[0] == '"' && line[1] == ' ' && !strncmp(line+2, key, key_len) && (!*(ptr = line+2+key_len) || *ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n')) { start = line; stop = start + strcspn(start, "\r\n"); stop += strspn(stop, "\r\n"); size_t tail_len = strlen(stop); memmove(start, stop, tail_len + 1); memset(start + tail_len, 0, stop - start); line = start; } else { line += strcspn(line, "\r\n"); line += strspn(line, "\r\n"); } } return 0; } static int step_size(pam_handle_t *pamh, const char *secret_filename, const char *buf) { const char *value = get_cfg_value(pamh, "STEP_SIZE", buf); if (!value) { // Default step size is 30. return 30; } else if (value == &oom) { // Out of memory. This is a fatal error. return 0; } char *endptr; errno = 0; const int step = (int)strtoul(value, &endptr, 10); if (errno || !*value || value == endptr || (*endptr && *endptr != ' ' && *endptr != '\t' && *endptr != '\n' && *endptr != '\r') || step < 1 || step > 60) { free((void *)value); log_message(LOG_ERR, pamh, "Invalid STEP_SIZE option in \"%s\"", secret_filename); return 0; } free((void *)value); return step; } static int get_timestamp(pam_handle_t *pamh, const char *secret_filename, const char **buf) { const int step = step_size(pamh, secret_filename, *buf); if (!step) { return 0; } return get_time()/step; } static long get_hotp_counter(pam_handle_t *pamh, const char *buf) { if (!buf) { return -1; } const char *counter_str = get_cfg_value(pamh, "HOTP_COUNTER", buf); if (counter_str == &oom) { // Out of memory. This is a fatal error return -1; } long counter = 0; if (counter_str) { counter = strtol(counter_str, NULL, 10); } free((void *)counter_str); return counter; } static int rate_limit(pam_handle_t *pamh, const char *secret_filename, int *updated, char **buf) { const char *value = get_cfg_value(pamh, "RATE_LIMIT", *buf); if (!value) { // Rate limiting is not enabled for this account return 0; } else if (value == &oom) { // Out of memory. This is a fatal error. return -1; } // Parse both the maximum number of login attempts and the time interval // that we are looking at. const char *endptr = value, *ptr; int attempts, interval; errno = 0; if (((attempts = (int)strtoul(ptr = endptr, (char **)&endptr, 10)) < 1) || ptr == endptr || attempts > 100 || errno || (*endptr != ' ' && *endptr != '\t') || ((interval = (int)strtoul(ptr = endptr, (char **)&endptr, 10)) < 1) || ptr == endptr || interval > 3600 || errno) { free((void *)value); log_message(LOG_ERR, pamh, "Invalid RATE_LIMIT option. Check \"%s\".", secret_filename); return -1; } // Parse the time stamps of all previous login attempts. const unsigned int now = get_time(); unsigned int *timestamps = malloc(sizeof(int)); if (!timestamps) { oom: free((void *)value); log_message(LOG_ERR, pamh, "Out of memory"); return -1; } timestamps[0] = now; int num_timestamps = 1; while (*endptr && *endptr != '\r' && *endptr != '\n') { unsigned int timestamp; errno = 0; if ((*endptr != ' ' && *endptr != '\t') || ((timestamp = (int)strtoul(ptr = endptr, (char **)&endptr, 10)), errno) || ptr == endptr) { free((void *)value); free(timestamps); log_message(LOG_ERR, pamh, "Invalid list of timestamps in RATE_LIMIT. " "Check \"%s\".", secret_filename); return -1; } num_timestamps++; unsigned int *tmp = (unsigned int *)realloc(timestamps, sizeof(int) * num_timestamps); if (!tmp) { free(timestamps); goto oom; } timestamps = tmp; timestamps[num_timestamps-1] = timestamp; } free((void *)value); value = NULL; // Sort time stamps, then prune all entries outside of the current time // interval. qsort(timestamps, num_timestamps, sizeof(int), comparator); int start = 0, stop = -1; for (int i = 0; i < num_timestamps; ++i) { if (timestamps[i] < now - interval) { start = i+1; } else if (timestamps[i] > now) { break; } stop = i; } // Error out, if there are too many login attempts. int exceeded = 0; if (stop - start + 1 > attempts) { exceeded = 1; start = stop - attempts + 1; } // Construct new list of timestamps within the current time interval. char* list; { const size_t list_size = 25 * (2 + (stop - start + 1)) + 4; list = malloc(list_size); if (!list) { free(timestamps); goto oom; } snprintf(list, list_size, "%d %d", attempts, interval); char *prnt = strchr(list, '\000'); for (int i = start; i <= stop; ++i) { prnt += snprintf(prnt, list_size-(prnt-list), " %u", timestamps[i]); } free(timestamps); } // Try to update RATE_LIMIT line. if (set_cfg_value(pamh, "RATE_LIMIT", list, buf) < 0) { free(list); return -1; } free(list); // Mark the state file as changed. *updated = 1; // If necessary, notify the user of the rate limiting that is in effect. if (exceeded) { log_message(LOG_ERR, pamh, "Too many concurrent login attempts (\"%s\"). Please try again.", secret_filename); return -1; } return 0; } static char *get_first_pass(pam_handle_t *pamh) { PAM_CONST void *password = NULL; if (pam_get_item(pamh, PAM_AUTHTOK, &password) == PAM_SUCCESS && password) { return strdup((const char *)password); } return NULL; } // Show error message to the user. static void conv_error(pam_handle_t *pamh, const char* text) { PAM_CONST struct pam_message msg = { .msg_style = PAM_ERROR_MSG, .msg = text, }; PAM_CONST struct pam_message *msgs = &msg; struct pam_response *resp = NULL; const int retval = converse(pamh, 1, &msgs, &resp); if (retval != PAM_SUCCESS) { log_message(LOG_ERR, pamh, "Failed to inform user of error"); } free(resp); } static char *request_pass(pam_handle_t *pamh, int echocode, PAM_CONST char *prompt) { // Query user for verification code PAM_CONST struct pam_message msg = { .msg_style = echocode, .msg = prompt }; PAM_CONST struct pam_message *msgs = &msg; struct pam_response *resp = NULL; int retval = converse(pamh, 1, &msgs, &resp); char *ret = NULL; if (retval != PAM_SUCCESS || resp == NULL || resp->resp == NULL || *resp->resp == '\000') { log_message(LOG_ERR, pamh, "Did not receive verification code from user"); if (retval == PAM_SUCCESS && resp && resp->resp) { ret = resp->resp; } } else { ret = resp->resp; } // Deallocate temporary storage if (resp) { if (!ret) { free(resp->resp); } free(resp); } return ret; } /* Checks for possible use of scratch codes. Returns -1 on error, 0 on success, * and 1, if no scratch code had been entered, and subsequent tests should be * applied. */ static int check_scratch_codes(pam_handle_t *pamh, const Params *params, const char *secret_filename, int *updated, char *buf, int code) { // Skip the first line. It contains the shared secret. char *ptr = buf + strcspn(buf, "\n"); // Check if this is one of the scratch codes char *endptr = NULL; for (;;) { // Skip newlines and blank lines while (*ptr == '\r' || *ptr == '\n') { ptr++; } // Skip any lines starting with double-quotes. They contain option fields if (*ptr == '"') { ptr += strcspn(ptr, "\n"); continue; } // Try to interpret the line as a scratch code errno = 0; const int scratchcode = (int)strtoul(ptr, &endptr, 10); // Sanity check that we read a valid scratch code. Scratchcodes are all // numeric eight-digit codes. There must not be any other information on // that line. if (errno || ptr == endptr || (*endptr != '\r' && *endptr != '\n' && *endptr) || scratchcode < 10*1000*1000 || scratchcode >= 100*1000*1000) { break; } // Check if the code matches if (scratchcode == code) { // Remove scratch code after using it while (*endptr == '\n' || *endptr == '\r') { ++endptr; } memmove(ptr, endptr, strlen(endptr) + 1); memset(strrchr(ptr, '\000'), 0, endptr - ptr + 1); // Mark the state file as changed *updated = 1; // Successfully removed scratch code. Allow user to log in. if(params->debug) { log_message(LOG_INFO, pamh, "debug: scratch code %d used and removed from \"%s\"", code, secret_filename); } return 0; } ptr = endptr; } // No scratch code has been used. Continue checking other types of codes. if(params->debug) { log_message(LOG_INFO, pamh, "debug: no scratch code used from \"%s\"", secret_filename); } return 1; } static int window_size(pam_handle_t *pamh, const char *secret_filename, const char *buf) { const char *value = get_cfg_value(pamh, "WINDOW_SIZE", buf); if (!value) { // Default window size is 3. This gives us one STEP_SIZE second // window before and after the current one. return 3; } else if (value == &oom) { // Out of memory. This is a fatal error. return 0; } char *endptr; errno = 0; const int window = (int)strtoul(value, &endptr, 10); if (errno || !*value || value == endptr || (*endptr && *endptr != ' ' && *endptr != '\t' && *endptr != '\n' && *endptr != '\r') || window < 1 || window > 100) { free((void *)value); log_message(LOG_ERR, pamh, "Invalid WINDOW_SIZE option in \"%s\"", secret_filename); return 0; } free((void *)value); return window; } /* If the DISALLOW_REUSE option has been set, record timestamps have been * used to log in successfully and disallow their reuse. * * Returns -1 on error, and 0 on success. */ static int invalidate_timebased_code(int tm, pam_handle_t *pamh, const char *secret_filename, int *updated, char **buf) { char *disallow = get_cfg_value(pamh, "DISALLOW_REUSE", *buf); if (!disallow) { // Reuse of tokens is not explicitly disallowed. Allow the login request // to proceed. return 0; } else if (disallow == &oom) { // Out of memory. This is a fatal error. return -1; } // Allow the user to customize the window size parameter. const int window = window_size(pamh, secret_filename, *buf); if (!window) { // The user configured a non-standard window size, but there was some // error with the value of this parameter. free((void *)disallow); return -1; } // The DISALLOW_REUSE option is followed by all known timestamps that are // currently unavailable for login. for (char *ptr = disallow; *ptr;) { // Skip white-space, if any ptr += strspn(ptr, " \t\r\n"); if (!*ptr) { break; } // Parse timestamp value. char *endptr; errno = 0; const int blocked = (int)strtoul(ptr, &endptr, 10); // Treat syntactically invalid options as an error if (errno || ptr == endptr || (*endptr != ' ' && *endptr != '\t' && *endptr != '\r' && *endptr != '\n' && *endptr)) { free((void *)disallow); return -1; } if (tm == blocked) { // The code is currently blocked from use. Disallow login. free((void *)disallow); const int step = step_size(pamh, secret_filename, *buf); if (!step) { return -1; } log_message(LOG_ERR, pamh, "Trying to reuse a previously used time-based code (\"%s\")." " Retry again in %d seconds. " "Warning! This might mean, you are currently subject to a " "man-in-the-middle attack.", secret_filename, step); return -1; } // If the blocked code is outside of the possible window of timestamps, // remove it from the file. if (blocked - tm >= window || tm - blocked >= window) { endptr += strspn(endptr, " \t"); memmove(ptr, endptr, strlen(endptr) + 1); } else { ptr = endptr; } } // Add the current timestamp to the list of disallowed timestamps. { const size_t resized_size = strlen(disallow) + 40; char *resized = realloc(disallow, resized_size); if (!resized) { free((void *)disallow); log_message(LOG_ERR, pamh, "Failed to allocate memory when updating \"%s\"", secret_filename); return -1; } disallow = resized; char* pos = strrchr(disallow, '\000'); snprintf(pos, resized_size-(pos-disallow), " %d" + !*disallow, tm); if (set_cfg_value(pamh, "DISALLOW_REUSE", disallow, buf) < 0) { free((void *)disallow); return -1; } free((void *)disallow); } // Mark the state file as changed *updated = 1; // Allow access. return 0; } /* Given an input value, this function computes the hash code that forms the * expected authentication token. */ #ifdef TESTING int compute_code(const uint8_t *secret, int secretLen, unsigned long value) __attribute__((visibility("default"))); #else static #endif int compute_code(const uint8_t *secret, int secretLen, unsigned long value) { uint8_t val[8]; for (int i = 8; i--; value >>= 8) { val[i] = value; } uint8_t hash[SHA1_DIGEST_LENGTH]; hmac_sha1(secret, secretLen, val, 8, hash, SHA1_DIGEST_LENGTH); explicit_bzero(val, sizeof(val)); const int offset = hash[SHA1_DIGEST_LENGTH - 1] & 0xF; unsigned int truncatedHash = 0; for (int i = 0; i < 4; ++i) { truncatedHash <<= 8; truncatedHash |= hash[offset + i]; } explicit_bzero(hash, sizeof(hash)); truncatedHash &= 0x7FFFFFFF; truncatedHash %= 1000000; return truncatedHash; } /* If a user repeated attempts to log in with the same time skew, remember * this skew factor for future login attempts. */ static int check_time_skew(pam_handle_t *pamh, int *updated, char **buf, int skew, int tm) { int rc = -1; // Parse current RESETTING_TIME_SKEW line, if any. char *resetting = get_cfg_value(pamh, "RESETTING_TIME_SKEW", *buf); if (resetting == &oom) { // Out of memory. This is a fatal error. return -1; } // If the user can produce a sequence of three consecutive codes that fall // within a day of the current time. And if he can enter these codes in // quick succession, then we allow the time skew to be reset. // N.B. the number "3" was picked so that it would not trigger the rate // limiting limit if set up with default parameters. unsigned int tms[3]; int skews[sizeof(tms)/sizeof(int)]; int num_entries = 0; if (resetting) { char *ptr = resetting; // Read the three most recent pairs of time stamps and skew values into // our arrays. while (*ptr && *ptr != '\r' && *ptr != '\n') { char *endptr; errno = 0; const unsigned int i = (int)strtoul(ptr, &endptr, 10); if (errno || ptr == endptr || (*endptr != '+' && *endptr != '-')) { break; } ptr = endptr; int j = (int)strtoul(ptr + 1, &endptr, 10); if (errno || ptr == endptr || (*endptr != ' ' && *endptr != '\t' && *endptr != '\r' && *endptr != '\n' && *endptr)) { break; } if (*ptr == '-') { j = -j; } if (num_entries == sizeof(tms)/sizeof(int)) { memmove(tms, tms+1, sizeof(tms)-sizeof(int)); memmove(skews, skews+1, sizeof(skews)-sizeof(int)); } else { ++num_entries; } tms[num_entries-1] = i; skews[num_entries-1] = j; ptr = endptr; } // If the user entered an identical code, assume they are just getting // desperate. This doesn't actually provide us with any useful data, // though. Don't change any state and hope the user keeps trying a few // more times. if (num_entries && tm + skew == tms[num_entries-1] + skews[num_entries-1]) { free((void *)resetting); return -1; } } free((void *)resetting); // Append new timestamp entry if (num_entries == sizeof(tms)/sizeof(int)) { memmove(tms, tms+1, sizeof(tms)-sizeof(int)); memmove(skews, skews+1, sizeof(skews)-sizeof(int)); } else { ++num_entries; } tms[num_entries-1] = tm; skews[num_entries-1] = skew; // Check if we have the required amount of valid entries. if (num_entries == sizeof(tms)/sizeof(int)) { unsigned int last_tm = tms[0]; int last_skew = skews[0]; int avg_skew = last_skew; for (int i = 1; i < sizeof(tms)/sizeof(int); ++i) { // Check that we have a consecutive sequence of timestamps with no big // gaps in between. Also check that the time skew stays constant. Allow // a minor amount of fuzziness on all parameters. if (tms[i] <= last_tm || tms[i] > last_tm+2 || last_skew - skew < -1 || last_skew - skew > 1) { goto keep_trying; } last_tm = tms[i]; last_skew = skews[i]; avg_skew += last_skew; } avg_skew /= (int)(sizeof(tms)/sizeof(int)); // The user entered the required number of valid codes in quick // succession. Establish a new valid time skew for all future login // attempts. char time_skew[40]; snprintf(time_skew, sizeof time_skew, "%d", avg_skew); if (set_cfg_value(pamh, "TIME_SKEW", time_skew, buf) < 0) { return -1; } rc = 0; keep_trying:; } // Set the new RESETTING_TIME_SKEW line, while the user is still trying // to reset the time skew. { const size_t reset_size = 80 * (sizeof(tms)/sizeof(int)); char reset[reset_size]; *reset = '\000'; if (rc) { for (int i = 0; i < num_entries; ++i) { char* pos = strrchr(reset, '\000'); snprintf(pos, reset_size-(pos-reset), " %d%+d" + !*reset, tms[i], skews[i]); } } if (set_cfg_value(pamh, "RESETTING_TIME_SKEW", reset, buf) < 0) { return -1; } } // Mark the state file as changed *updated = 1; return rc; } /* Checks for time based verification code. Returns -1 on error, 0 on success, * and 1, if no time based code had been entered, and subsequent tests should * be applied. */ static int check_timebased_code(pam_handle_t *pamh, const char*secret_filename, int *updated, char **buf, const uint8_t*secret, int secretLen, int code, Params *params) { if (!is_totp(*buf)) { // The secret file does not actually contain information for a time-based // code. Return to caller and see if any other authentication methods // apply. return 1; } if (code < 0 || code >= 1000000) { // All time based verification codes are no longer than six digits. return 1; } // Compute verification codes and compare them with user input const int tm = get_timestamp(pamh, secret_filename, (const char **)buf); if (!tm) { return -1; } const char *skew_str = get_cfg_value(pamh, "TIME_SKEW", *buf); if (skew_str == &oom) { // Out of memory. This is a fatal error return -1; } int skew = 0; if (skew_str) { skew = (int)strtol(skew_str, NULL, 10); } free((void *)skew_str); const int window = window_size(pamh, secret_filename, *buf); if (!window) { return -1; } for (int i = -((window-1)/2); i <= window/2; ++i) { const unsigned int hash = compute_code(secret, secretLen, tm + skew + i); if (hash == (unsigned int)code) { return invalidate_timebased_code(tm + skew + i, pamh, secret_filename, updated, buf); } } if (!params->noskewadj) { // The most common failure mode is for the clocks to be insufficiently // synchronized. We can detect this and store a skew value for future // use. skew = 1000000; for (int i = 0; i < 25*60; ++i) { unsigned int hash = compute_code(secret, secretLen, tm - i); if (hash == (unsigned int)code && skew == 1000000) { // Don't short-circuit out of the loop as the obvious difference in // computation time could be a signal that is valuable to an attacker. skew = -i; } hash = compute_code(secret, secretLen, tm + i); if (hash == (unsigned int)code && skew == 1000000) { skew = i; } } if (skew != 1000000) { if(params->debug) { log_message(LOG_INFO, pamh, "debug: time skew adjusted"); } return check_time_skew(pamh, updated, buf, skew, tm); } } return 1; } /* * Add a 'config' variable that says we logged in from a particular place * at a particular time. Only remembers the last 10 logins, which * are replaced in LRU order. * * Returns 0 on success. */ int update_logindetails(pam_handle_t *pamh, const Params *params, char **buf) { const char *rhost = get_rhost(pamh, params); const time_t now = get_time(); time_t oldest = now; // Oldest entry seen so far. int oldest_index = -1; // Index of oldest entry, due for replacement. int found = 0; // Entry for this rhost found. char name[] = "LAST "; // Config name template. if (rhost == NULL) { return -1; } for (int i = 0; i < 10; i++) { // // Get LAST<n> cfg value. // name[4] = i + '0'; char *line = get_cfg_value(pamh, name, *buf); if (line == &oom) { /* Fatal! */ return -1; } if (!line) { /* Make first empty line the oldest */ if (oldest) { oldest_index = i; oldest = 0; } continue; } // // Parse value. // // Max len of ipv6 address is 8*4 digits plus 7 colons. // Plus trailing NUL is 40. // But RHOST can be FQDN, and by RFC1035 that's 255 characters as max. char host[256]; unsigned long when = 0; // Timestamp of current entry. const int scanf_rc = sscanf(line, " %255[0-9a-zA-Z:.-] %lu ", host, &when); free(line); if (scanf_rc != 2) { log_message(LOG_ERR, pamh, "Malformed LAST%d line", i); continue; } if (!strcmp(host, rhost)) { found = 1; break; } if (when < oldest) { oldest_index = i; oldest = when; } } if (!found) { /* Loop completed all ten iterations */ name[4] = oldest_index + '0'; } /* * Max length in decimal digits of a 64 bit number is (64 log 2) + 1 * Plus space and NUL termination, is 23. * Max len of hostname is 255. */ char value[255+23+1]; memset(value, 0, sizeof value); snprintf(value, sizeof value, "%s %lu", rhost, (unsigned long)now); if (set_cfg_value(pamh, name, value, buf) < 0) { log_message(LOG_WARNING, pamh, "Failed to set cfg value for login host"); } return 0; } /* * Return non-zero if the last login from the same host as this one was * successfully authenticated within the grace period. */ int within_grace_period(pam_handle_t *pamh, const Params *params, const char *buf) { const char *rhost = get_rhost(pamh, params); const time_t now = get_time(); const time_t grace = params->grace_period; unsigned long when = 0; char match[128]; if (rhost == NULL) { return 0; } snprintf(match, sizeof match, " %s %%lu ", rhost); for (int i = 0; i < 10; i++) { static char name[] = "LAST0"; name[4] = i + '0'; char* line = get_cfg_value(pamh, name, buf); if (line == &oom) { /* Fatal! */ return 0; } if (!line) { continue; } if (sscanf(line, match, &when) == 1) { free(line); break; } free(line); } if (when == 0) { /* No match */ return 0; } return (when + grace > now); } /* Checks for counter based verification code. Returns -1 on error, 0 on * success, and 1, if no counter based code had been entered, and subsequent * tests should be applied. */ static int check_counterbased_code(pam_handle_t *pamh, const char*secret_filename, int *updated, char **buf, const uint8_t*secret, int secretLen, int code, long hotp_counter, int *must_advance_counter) { if (hotp_counter < 1) { // The secret file did not actually contain information for a counter-based // code. Return to caller and see if any other authentication methods // apply. return 1; } if (code < 0 || code >= 1000000) { // All counter based verification codes are no longer than six digits. return 1; } // Compute [window_size] verification codes and compare them with user input. // Future codes are allowed in case the user computed but did not use a code. const int window = window_size(pamh, secret_filename, *buf); if (!window) { return -1; } for (int i = 0; i < window; ++i) { const unsigned int hash = compute_code(secret, secretLen, hotp_counter + i); if (hash == (unsigned int)code) { char counter_str[40]; snprintf(counter_str, sizeof counter_str, "%ld", hotp_counter + i + 1); if (set_cfg_value(pamh, "HOTP_COUNTER", counter_str, buf) < 0) { return -1; } *updated = 1; *must_advance_counter = 0; return 0; } } *must_advance_counter = 1; return 1; } // parse a user name. // input: user name // output: uid // return: 0 on success. static int parse_user(pam_handle_t *pamh, const char *name, uid_t *uid) { char *endptr; errno = 0; const long l = strtol(name, &endptr, 10); if (!errno && endptr != name && l >= 0 && l <= INT_MAX) { *uid = (uid_t)l; return 0; } const size_t len = getpwnam_buf_max_size(); char *buf = malloc(len); if (!buf) { log_message(LOG_ERR, pamh, "Out of memory"); return -1; } struct passwd pwbuf, *pw; if (getpwnam_r(name, &pwbuf, buf, len, &pw) || !pw) { free(buf); log_message(LOG_ERR, pamh, "Failed to look up user \"%s\"", name); return -1; } *uid = pw->pw_uid; free(buf); return 0; } static int parse_args(pam_handle_t *pamh, int argc, const char **argv, Params *params) { params->debug = 0; params->echocode = PAM_PROMPT_ECHO_OFF; for (int i = 0; i < argc; ++i) { if (!strncmp(argv[i], "secret=", 7)) { params->secret_filename_spec = argv[i] + 7; } else if (!strncmp(argv[i], "authtok_prompt=", 15)) { params->authtok_prompt = argv[i] + 15; } else if (!strncmp(argv[i], "user=", 5)) { uid_t uid; if (parse_user(pamh, argv[i] + 5, &uid) < 0) { return -1; } params->fixed_uid = 1; params->uid = uid; } else if (!strncmp(argv[i], "allowed_perm=", 13)) { char *remainder = NULL; const int perm = (int)strtol(argv[i] + 13, &remainder, 8); if (perm == 0 || strlen(remainder) != 0) { log_message(LOG_ERR, pamh, "Invalid permissions in setting \"%s\"." " allowed_perm setting must be a positive octal integer.", argv[i]); return -1; } params->allowed_perm = perm; } else if (!strcmp(argv[i], "no_strict_owner")) { params->no_strict_owner = 1; } else if (!strcmp(argv[i], "debug")) { params->debug = 1; } else if (!strcmp(argv[i], "try_first_pass")) { params->pass_mode = TRY_FIRST_PASS; } else if (!strcmp(argv[i], "use_first_pass")) { params->pass_mode = USE_FIRST_PASS; } else if (!strcmp(argv[i], "forward_pass")) { params->forward_pass = 1; } else if (!strcmp(argv[i], "noskewadj")) { params->noskewadj = 1; } else if (!strcmp(argv[i], "no_increment_hotp")) { params->no_increment_hotp = 1; } else if (!strcmp(argv[i], "nullok")) { params->nullok = NULLOK; } else if (!strcmp(argv[i], "allow_readonly")) { params->allow_readonly = 1; } else if (!strcmp(argv[i], "echo-verification-code") || !strcmp(argv[i], "echo_verification_code")) { params->echocode = PAM_PROMPT_ECHO_ON; } else if (!strncmp(argv[i], "grace_period=", 13)) { char *remainder = NULL; const time_t grace = (time_t)strtol(argv[i] + 13, &remainder, 10); if (grace < 0 || *remainder) { log_message(LOG_ERR, pamh, "Invalid value in setting \"%s\"." "grace_period must be a positive number of seconds.", argv[i]); return -1; } params->grace_period = grace; } else { log_message(LOG_ERR, pamh, "Unrecognized option \"%s\"", argv[i]); return -1; } } return 0; } static int google_authenticator(pam_handle_t *pamh, int argc, const char **argv) { int rc = PAM_AUTH_ERR; uid_t uid = -1; int old_uid = -1, old_gid = -1, fd = -1; char *buf = NULL; struct stat orig_stat = { 0 }; uint8_t *secret = NULL; int secretLen = 0; // Handle optional arguments that configure our PAM module Params params = { 0 }; params.allowed_perm = 0600; if (parse_args(pamh, argc, argv, ¶ms) < 0) { return rc; } const char *prompt = params.authtok_prompt ? params.authtok_prompt : (params.forward_pass ? PWCODE_PROMPT : CODE_PROMPT); // Read and process status file, then ask the user for the verification code. int early_updated = 0, updated = 0; const char* const username = get_user_name(pamh, ¶ms); char* const secret_filename = get_secret_filename(pamh, ¶ms, username, &uid); int stopped_by_rate_limit = 0; // Drop privileges. { const char* drop_username = username; // If user doesn't exist, use 'nobody'. if (uid == -1) { drop_username = nobody; if (parse_user(pamh, drop_username, &uid)) { // If 'nobody' doesn't exist, bail. We need to drop privs to *someone*. goto out; } } if (drop_privileges(pamh, drop_username, uid, &old_uid, &old_gid)) { // Don't allow to continue without dropping privs. goto out; } } if (secret_filename) { fd = open_secret_file(pamh, secret_filename, ¶ms, username, uid, &orig_stat); if (fd >= 0) { buf = read_file_contents(pamh, ¶ms, secret_filename, &fd, orig_stat.st_size); } if (buf) { if (rate_limit(pamh, secret_filename, &early_updated, &buf) >= 0) { secret = get_shared_secret(pamh, ¶ms, secret_filename, buf, &secretLen); } else { stopped_by_rate_limit=1; } } } const long hotp_counter = get_hotp_counter(pamh, buf); /* * Check to see if a successful login from the same host happened * within the grace period. If it did, then allow login without * an additional code. */ if (buf && within_grace_period(pamh, ¶ms, buf)) { rc = PAM_SUCCESS; log_message(LOG_INFO, pamh, "within grace period: \"%s\"", username); goto out; } // Only if nullok and we do not have a code will we NOT ask for a code. // In all other cases (i.e "have code" and "no nullok and no code") we DO ask for a code. if (!stopped_by_rate_limit && ( secret || params.nullok != SECRETNOTFOUND ) ) { if (!secret) { log_message(LOG_WARNING , pamh, "No secret configured for user %s, asking for code anyway.", username); } int must_advance_counter = 0; char *pw = NULL, *saved_pw = NULL; for (int mode = 0; mode < 4; ++mode) { // In the case of TRY_FIRST_PASS, we don't actually know whether we // get the verification code from the system password or from prompting // the user. We need to attempt both. // This only works correctly, if all failed attempts leave the global // state unchanged. if (updated || pw) { // Oops. There is something wrong with the internal logic of our // code. This error should never trigger. The unittest checks for // this. if (pw) { explicit_bzero(pw, strlen(pw)); free(pw); pw = NULL; } rc = PAM_AUTH_ERR; break; } switch (mode) { case 0: // Extract possible verification code case 1: // Extract possible scratch code if (params.pass_mode == USE_FIRST_PASS || params.pass_mode == TRY_FIRST_PASS) { pw = get_first_pass(pamh); } break; default: if (mode != 2 && // Prompt for pw and possible verification code mode != 3) { // Prompt for pw and possible scratch code rc = PAM_AUTH_ERR; continue; } if (params.pass_mode == PROMPT || params.pass_mode == TRY_FIRST_PASS) { if (!saved_pw) { // If forwarding the password to the next stacked PAM module, // we cannot tell the difference between an eight digit scratch // code or a two digit password immediately followed by a six // digit verification code. We have to loop and try both // options. saved_pw = request_pass(pamh, params.echocode, prompt); } if (saved_pw) { pw = strdup(saved_pw); } } break; } if (!pw) { continue; } // We are often dealing with a combined password and verification // code. Separate them now. const int pw_len = strlen(pw); const int expected_len = mode & 1 ? 8 : 6; char ch; // Full OpenSSH "bad password" is "\b\n\r\177INCORRECT", capped // to original password length. if (pw_len > 0 && pw[0] == '\b') { log_message(LOG_INFO, pamh, "Dummy password supplied by PAM." " Did OpenSSH 'PermitRootLogin <anything but yes>' or some" " other config block this login?"); } if (pw_len < expected_len || // Verification are six digits starting with '0'..'9', // scratch codes are eight digits starting with '1'..'9' (ch = pw[pw_len - expected_len]) > '9' || ch < (expected_len == 8 ? '1' : '0')) { invalid: explicit_bzero(pw, pw_len); free(pw); pw = NULL; continue; } char *endptr; errno = 0; const long l = strtol(pw + pw_len - expected_len, &endptr, 10); if (errno || l < 0 || *endptr) { goto invalid; } const int code = (int)l; memset(pw + pw_len - expected_len, 0, expected_len); if ((mode == 2 || mode == 3) && !params.forward_pass) { // We are explicitly configured so that we don't try to share // the password with any other stacked PAM module. We must // therefore verify that the user entered just the verification // code, but no password. if (*pw) { goto invalid; } } // Only if we actually have a secret will we try to verify the code // In all other cases will we just remain at PAM_AUTH_ERR if (secret) { // Check all possible types of verification codes. switch (check_scratch_codes(pamh, ¶ms, secret_filename, &updated, buf, code)) { case 1: if (hotp_counter > 0) { switch (check_counterbased_code(pamh, secret_filename, &updated, &buf, secret, secretLen, code, hotp_counter, &must_advance_counter)) { case 0: rc = PAM_SUCCESS; break; case 1: goto invalid; default: break; } } else { switch (check_timebased_code(pamh, secret_filename, &updated, &buf, secret, secretLen, code, ¶ms)) { case 0: rc = PAM_SUCCESS; break; case 1: goto invalid; default: break; } } break; case 0: rc = PAM_SUCCESS; break; default: break; } break; } } // Update the system password, if we were asked to forward // the system password. We already removed the verification // code from the end of the password. if (rc == PAM_SUCCESS && params.forward_pass) { if (!pw || pam_set_item(pamh, PAM_AUTHTOK, pw) != PAM_SUCCESS) { rc = PAM_AUTH_ERR; } } // Clear out password and deallocate memory if (pw) { explicit_bzero(pw,strlen(pw)); free(pw); } if (saved_pw) { explicit_bzero(saved_pw, strlen(saved_pw)); free(saved_pw); } // If an hotp login attempt has been made, the counter must always be // advanced by at least one, unless this has been disabled. if (!params.no_increment_hotp && must_advance_counter) { char counter_str[40]; snprintf(counter_str, sizeof counter_str, "%ld", hotp_counter + 1); if (set_cfg_value(pamh, "HOTP_COUNTER", counter_str, &buf) < 0) { rc = PAM_AUTH_ERR; } updated = 1; } // Display a success or error message if (rc == PAM_SUCCESS) { log_message(LOG_INFO , pamh, "Accepted google_authenticator for %s", username); if (params.grace_period != 0) { updated = 1; if (update_logindetails(pamh, ¶ms, &buf)) { log_message(LOG_ERR, pamh, "Failed to store grace_period timestamp in config"); } } } else { log_message(LOG_ERR, pamh, "Invalid verification code for %s", username); } } // If the user has not created a state file with a shared secret, and if // the administrator set the "nullok" option, this PAM module completes // without saying success or failure, without ever prompting the user. // It's not a failure since "nullok" was specified, and it's not a success // because it must be distinguishable from "good credentials given" in // case the PAM config considers this module "sufficient". // (or more complex equivalents) if (params.nullok == SECRETNOTFOUND) { rc = PAM_IGNORE; } // Persist the new state. if (early_updated || updated) { int err; if ((err = write_file_contents(pamh, ¶ms, secret_filename, &orig_stat, buf))) { // Inform user of error if the error is clearly a system error // and not an auth error. char s[1024]; switch (err) { case EPERM: case ENOSPC: case EROFS: case EIO: case EDQUOT: snprintf(s, sizeof(s), "Error \"%s\" while writing config", strerror(err)); conv_error(pamh, s); } // If allow_readonly parameter is defined than ignore write errors and // allow user to login. if (!params.allow_readonly) { // Could not persist new state. Deny access. rc = PAM_AUTH_ERR; } } } out: if (params.debug) { log_message(LOG_INFO, pamh, "debug: end of google_authenticator for \"%s\". Result: %s", username, pam_strerror(pamh, rc)); } if (fd >= 0) { close(fd); } if (old_gid >= 0) { if (setgroup(old_gid) >= 0 && setgroup(old_gid) == old_gid) { old_gid = -1; } } if (old_uid >= 0) { if (setuser(old_uid) < 0 || setuser(old_uid) != old_uid) { log_message(LOG_EMERG, pamh, "We switched users from %d to %d, " "but can't switch back", old_uid, uid); } } free(secret_filename); // Clean up if (buf) { explicit_bzero(buf, strlen(buf)); free(buf); } if (secret) { explicit_bzero(secret, secretLen); free(secret); } return rc; } #ifndef UNUSED_ATTR # if __GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) # define UNUSED_ATTR __attribute__((__unused__)) # else # define UNUSED_ATTR # endif #endif PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED_ATTR, int argc, const char **argv) { return google_authenticator(pamh, argc, argv); } PAM_EXTERN int pam_sm_setcred (pam_handle_t *pamh UNUSED_ATTR, int flags UNUSED_ATTR, int argc UNUSED_ATTR, const char **argv UNUSED_ATTR) { return PAM_SUCCESS; } #ifdef PAM_STATIC struct pam_module _pam_listfile_modstruct = { MODULE_NAME, pam_sm_authenticate, pam_sm_setcred, NULL, NULL, NULL, NULL }; #endif /* ---- Emacs Variables ---- * Local Variables: * c-basic-offset: 2 * indent-tabs-mode: nil * End: */ 07070100000024000081A40000000000000000000000016627DD8100002C2A000000000000000000000000000000000000002C00000000google-authenticator-libpam-1.10/src/sha1.c/* * Copyright 2010 Google Inc. * Author: Markus Gutschke * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * An earlier version of this file was originally released into the public * domain by its authors. It has been modified to make the code compile and * link as part of the Google Authenticator project. These changes are * copyrighted by Google Inc. and released under the Apache License, * Version 2.0. * * The previous authors' terms are included below: */ /***************************************************************************** * * File: sha1.c * * Purpose: Implementation of the SHA1 message-digest algorithm. * * NIST Secure Hash Algorithm * Heavily modified by Uwe Hollerbach <uh@alumni.caltech edu> * from Peter C. Gutmann's implementation as found in * Applied Cryptography by Bruce Schneier * Further modifications to include the "UNRAVEL" stuff, below * * This code is in the public domain * ***************************************************************************** */ #define _BSD_SOURCE #define _DEFAULT_SOURCE #include <sys/types.h> // Defines BYTE_ORDER, iff _BSD_SOURCE is defined #include <string.h> #include "sha1.h" #if !defined(BYTE_ORDER) #if defined(_BIG_ENDIAN) #define BYTE_ORDER 4321 #elif defined(_LITTLE_ENDIAN) #define BYTE_ORDER 1234 #else #error Need to define BYTE_ORDER #endif #endif #ifndef TRUNC32 #define TRUNC32(x) ((x) & 0xffffffffL) #endif /* SHA f()-functions */ #define f1(x,y,z) ((x & y) | (~x & z)) #define f2(x,y,z) (x ^ y ^ z) #define f3(x,y,z) ((x & y) | (x & z) | (y & z)) #define f4(x,y,z) (x ^ y ^ z) /* SHA constants */ #define CONST1 0x5a827999L #define CONST2 0x6ed9eba1L #define CONST3 0x8f1bbcdcL #define CONST4 0xca62c1d6L /* truncate to 32 bits -- should be a null op on 32-bit machines */ #define T32(x) ((x) & 0xffffffffL) /* 32-bit rotate */ #define R32(x,n) T32(((x << n) | (x >> (32 - n)))) /* the generic case, for when the overall rotation is not unraveled */ #define FG(n) \ T = T32(R32(A,5) + f##n(B,C,D) + E + *WP++ + CONST##n); \ E = D; D = C; C = R32(B,30); B = A; A = T /* specific cases, for when the overall rotation is unraveled */ #define FA(n) \ T = T32(R32(A,5) + f##n(B,C,D) + E + *WP++ + CONST##n); B = R32(B,30) #define FB(n) \ E = T32(R32(T,5) + f##n(A,B,C) + D + *WP++ + CONST##n); A = R32(A,30) #define FC(n) \ D = T32(R32(E,5) + f##n(T,A,B) + C + *WP++ + CONST##n); T = R32(T,30) #define FD(n) \ C = T32(R32(D,5) + f##n(E,T,A) + B + *WP++ + CONST##n); E = R32(E,30) #define FE(n) \ B = T32(R32(C,5) + f##n(D,E,T) + A + *WP++ + CONST##n); D = R32(D,30) #define FT(n) \ A = T32(R32(B,5) + f##n(C,D,E) + T + *WP++ + CONST##n); C = R32(C,30) static void sha1_transform(SHA1_INFO *sha1_info) { int i; uint8_t *dp; uint32_t T, A, B, C, D, E, W[80], *WP; dp = sha1_info->data; #undef SWAP_DONE #if BYTE_ORDER == 1234 #define SWAP_DONE for (i = 0; i < 16; ++i) { T = *((uint32_t *) dp); dp += 4; W[i] = ((T << 24) & 0xff000000) | ((T << 8) & 0x00ff0000) | ((T >> 8) & 0x0000ff00) | ((T >> 24) & 0x000000ff); } #endif #if BYTE_ORDER == 4321 #define SWAP_DONE for (i = 0; i < 16; ++i) { T = *((uint32_t *) dp); dp += 4; W[i] = TRUNC32(T); } #endif #if BYTE_ORDER == 12345678 #define SWAP_DONE for (i = 0; i < 16; i += 2) { T = *((uint32_t *) dp); dp += 8; W[i] = ((T << 24) & 0xff000000) | ((T << 8) & 0x00ff0000) | ((T >> 8) & 0x0000ff00) | ((T >> 24) & 0x000000ff); T >>= 32; W[i+1] = ((T << 24) & 0xff000000) | ((T << 8) & 0x00ff0000) | ((T >> 8) & 0x0000ff00) | ((T >> 24) & 0x000000ff); } #endif #if BYTE_ORDER == 87654321 #define SWAP_DONE for (i = 0; i < 16; i += 2) { T = *((uint32_t *) dp); dp += 8; W[i] = TRUNC32(T >> 32); W[i+1] = TRUNC32(T); } #endif #ifndef SWAP_DONE #define SWAP_DONE for (i = 0; i < 16; ++i) { T = *((uint32_t *) dp); dp += 4; W[i] = TRUNC32(T); } #endif /* SWAP_DONE */ for (i = 16; i < 80; ++i) { W[i] = W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]; W[i] = R32(W[i], 1); } A = sha1_info->digest[0]; B = sha1_info->digest[1]; C = sha1_info->digest[2]; D = sha1_info->digest[3]; E = sha1_info->digest[4]; WP = W; #ifdef UNRAVEL FA(1); FB(1); FC(1); FD(1); FE(1); FT(1); FA(1); FB(1); FC(1); FD(1); FE(1); FT(1); FA(1); FB(1); FC(1); FD(1); FE(1); FT(1); FA(1); FB(1); FC(2); FD(2); FE(2); FT(2); FA(2); FB(2); FC(2); FD(2); FE(2); FT(2); FA(2); FB(2); FC(2); FD(2); FE(2); FT(2); FA(2); FB(2); FC(2); FD(2); FE(3); FT(3); FA(3); FB(3); FC(3); FD(3); FE(3); FT(3); FA(3); FB(3); FC(3); FD(3); FE(3); FT(3); FA(3); FB(3); FC(3); FD(3); FE(3); FT(3); FA(4); FB(4); FC(4); FD(4); FE(4); FT(4); FA(4); FB(4); FC(4); FD(4); FE(4); FT(4); FA(4); FB(4); FC(4); FD(4); FE(4); FT(4); FA(4); FB(4); sha1_info->digest[0] = T32(sha1_info->digest[0] + E); sha1_info->digest[1] = T32(sha1_info->digest[1] + T); sha1_info->digest[2] = T32(sha1_info->digest[2] + A); sha1_info->digest[3] = T32(sha1_info->digest[3] + B); sha1_info->digest[4] = T32(sha1_info->digest[4] + C); #else /* !UNRAVEL */ #ifdef UNROLL_LOOPS FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); #else /* !UNROLL_LOOPS */ for (i = 0; i < 20; ++i) { FG(1); } for (i = 20; i < 40; ++i) { FG(2); } for (i = 40; i < 60; ++i) { FG(3); } for (i = 60; i < 80; ++i) { FG(4); } #endif /* !UNROLL_LOOPS */ sha1_info->digest[0] = T32(sha1_info->digest[0] + A); sha1_info->digest[1] = T32(sha1_info->digest[1] + B); sha1_info->digest[2] = T32(sha1_info->digest[2] + C); sha1_info->digest[3] = T32(sha1_info->digest[3] + D); sha1_info->digest[4] = T32(sha1_info->digest[4] + E); #endif /* !UNRAVEL */ } /* initialize the SHA digest */ void sha1_init(SHA1_INFO *sha1_info) { sha1_info->digest[0] = 0x67452301L; sha1_info->digest[1] = 0xefcdab89L; sha1_info->digest[2] = 0x98badcfeL; sha1_info->digest[3] = 0x10325476L; sha1_info->digest[4] = 0xc3d2e1f0L; sha1_info->count_lo = 0L; sha1_info->count_hi = 0L; sha1_info->local = 0; } /* update the SHA digest */ void sha1_update(SHA1_INFO *sha1_info, const uint8_t *buffer, int count) { uint32_t clo; clo = T32(sha1_info->count_lo + ((uint32_t) count << 3)); if (clo < sha1_info->count_lo) { ++sha1_info->count_hi; } sha1_info->count_lo = clo; sha1_info->count_hi += (uint32_t) count >> 29; if (sha1_info->local) { int i = SHA1_BLOCKSIZE - sha1_info->local; if (i > count) { i = count; } memcpy(((uint8_t *) sha1_info->data) + sha1_info->local, buffer, i); count -= i; buffer += i; sha1_info->local += i; if (sha1_info->local == SHA1_BLOCKSIZE) { sha1_transform(sha1_info); } else { return; } } while (count >= SHA1_BLOCKSIZE) { memcpy(sha1_info->data, buffer, SHA1_BLOCKSIZE); buffer += SHA1_BLOCKSIZE; count -= SHA1_BLOCKSIZE; sha1_transform(sha1_info); } memcpy(sha1_info->data, buffer, count); sha1_info->local = count; } static void sha1_transform_and_copy(unsigned char digest[20], SHA1_INFO *sha1_info) { sha1_transform(sha1_info); digest[ 0] = (unsigned char) ((sha1_info->digest[0] >> 24) & 0xff); digest[ 1] = (unsigned char) ((sha1_info->digest[0] >> 16) & 0xff); digest[ 2] = (unsigned char) ((sha1_info->digest[0] >> 8) & 0xff); digest[ 3] = (unsigned char) ((sha1_info->digest[0] ) & 0xff); digest[ 4] = (unsigned char) ((sha1_info->digest[1] >> 24) & 0xff); digest[ 5] = (unsigned char) ((sha1_info->digest[1] >> 16) & 0xff); digest[ 6] = (unsigned char) ((sha1_info->digest[1] >> 8) & 0xff); digest[ 7] = (unsigned char) ((sha1_info->digest[1] ) & 0xff); digest[ 8] = (unsigned char) ((sha1_info->digest[2] >> 24) & 0xff); digest[ 9] = (unsigned char) ((sha1_info->digest[2] >> 16) & 0xff); digest[10] = (unsigned char) ((sha1_info->digest[2] >> 8) & 0xff); digest[11] = (unsigned char) ((sha1_info->digest[2] ) & 0xff); digest[12] = (unsigned char) ((sha1_info->digest[3] >> 24) & 0xff); digest[13] = (unsigned char) ((sha1_info->digest[3] >> 16) & 0xff); digest[14] = (unsigned char) ((sha1_info->digest[3] >> 8) & 0xff); digest[15] = (unsigned char) ((sha1_info->digest[3] ) & 0xff); digest[16] = (unsigned char) ((sha1_info->digest[4] >> 24) & 0xff); digest[17] = (unsigned char) ((sha1_info->digest[4] >> 16) & 0xff); digest[18] = (unsigned char) ((sha1_info->digest[4] >> 8) & 0xff); digest[19] = (unsigned char) ((sha1_info->digest[4] ) & 0xff); } /* finish computing the SHA digest */ void sha1_final(SHA1_INFO *sha1_info, uint8_t digest[20]) { int count; uint32_t lo_bit_count, hi_bit_count; lo_bit_count = sha1_info->count_lo; hi_bit_count = sha1_info->count_hi; count = (int) ((lo_bit_count >> 3) & 0x3f); ((uint8_t *) sha1_info->data)[count++] = 0x80; if (count > SHA1_BLOCKSIZE - 8) { memset(((uint8_t *) sha1_info->data) + count, 0, SHA1_BLOCKSIZE - count); sha1_transform(sha1_info); memset((uint8_t *) sha1_info->data, 0, SHA1_BLOCKSIZE - 8); } else { memset(((uint8_t *) sha1_info->data) + count, 0, SHA1_BLOCKSIZE - 8 - count); } sha1_info->data[56] = (uint8_t)((hi_bit_count >> 24) & 0xff); sha1_info->data[57] = (uint8_t)((hi_bit_count >> 16) & 0xff); sha1_info->data[58] = (uint8_t)((hi_bit_count >> 8) & 0xff); sha1_info->data[59] = (uint8_t)((hi_bit_count >> 0) & 0xff); sha1_info->data[60] = (uint8_t)((lo_bit_count >> 24) & 0xff); sha1_info->data[61] = (uint8_t)((lo_bit_count >> 16) & 0xff); sha1_info->data[62] = (uint8_t)((lo_bit_count >> 8) & 0xff); sha1_info->data[63] = (uint8_t)((lo_bit_count >> 0) & 0xff); sha1_transform_and_copy(digest, sha1_info); } /***EOF***/ 07070100000025000081A40000000000000000000000016627DD81000004A5000000000000000000000000000000000000002C00000000google-authenticator-libpam-1.10/src/sha1.h// SHA1 header file // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef SHA1_H__ #define SHA1_H__ #include <stdint.h> #define SHA1_BLOCKSIZE 64 #define SHA1_DIGEST_LENGTH 20 typedef struct { uint32_t digest[8]; uint32_t count_lo, count_hi; uint8_t data[SHA1_BLOCKSIZE]; int local; } SHA1_INFO; void sha1_init(SHA1_INFO *sha1_info) __attribute__((visibility("hidden"))); void sha1_update(SHA1_INFO *sha1_info, const uint8_t *buffer, int count) __attribute__((visibility("hidden"))); void sha1_final(SHA1_INFO *sha1_info, uint8_t digest[20]) __attribute__((visibility("hidden"))); #endif 07070100000026000081A40000000000000000000000016627DD8100000594000000000000000000000000000000000000002C00000000google-authenticator-libpam-1.10/src/util.c/* * Copyright 2017 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * An earlier version of this file was originally released into the public * domain by its authors. It has been modified to make the code compile and * link as part of the Google Authenticator project. These changes are * copyrighted by Google Inc. and released under the Apache License, * Version 2.0. * * The previous authors' terms are included below: */ /***************************************************************************** * * File: util.c * * Purpose: Collection of cross file utility functions. * * This code is in the public domain * ***************************************************************************** */ #include "config.h" #include <string.h> #ifndef HAVE_EXPLICIT_BZERO void explicit_bzero(void *s, size_t len) { memset(s, '\0', len); asm volatile ("":::"memory"); } #endif 07070100000027000081A40000000000000000000000016627DD81000002C7000000000000000000000000000000000000002C00000000google-authenticator-libpam-1.10/src/util.h// util header file // // Copyright 2017 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "config.h" #ifndef HAVE_EXPLICIT_BZERO void explicit_bzero(void *s, size_t len); #endif 07070100000028000041ED0000000000000000000000026627DD8100000000000000000000000000000000000000000000002700000000google-authenticator-libpam-1.10/tests07070100000029000081ED0000000000000000000000016627DD8100000119000000000000000000000000000000000000003600000000google-authenticator-libpam-1.10/tests/base32_test.sh#!/bin/bash a=0 while [ $a -lt 150 ] ;do dd if=/dev/urandom bs=$RANDOM count=1 of=testfile > /dev/null 2>&1 cat testfile | ./base32 -e | ./base32 -d > testfile.out if ! cmp -s testfile testfile.out ; then echo FAILED exit 1 fi a=$((a + 1)) done rm testfile testfile.out 0707010000002A000081A40000000000000000000000016627DD8100005C94000000000000000000000000000000000000004B00000000google-authenticator-libpam-1.10/tests/pam_google_authenticator_unittest.c// Unittest for the PAM module. This is part of the Google Authenticator // project. // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "config.h" #include <assert.h> #include <dlfcn.h> #include <fcntl.h> #include <security/pam_appl.h> #include <security/pam_modules.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include "../src/base32.h" #include "../src/hmac.h" #if !defined(PAM_BAD_ITEM) // FreeBSD does not know about PAM_BAD_ITEM. And PAM_SYMBOL_ERR is an "enum", // we can't test for it at compile-time. #define PAM_BAD_ITEM PAM_SYMBOL_ERR #endif static PAM_CONST char pw[] = "0123456789"; static char *response = ""; static void *pam_module; static enum { TWO_PROMPTS, COMBINED_PASSWORD, COMBINED_PROMPT } conv_mode; static int num_prompts_shown = 0; static int conversation(int num_msg, PAM_CONST struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { // Keep track of how often the conversation callback is executed. ++num_prompts_shown; if (conv_mode == COMBINED_PASSWORD) { return PAM_CONV_ERR; } if (num_msg == 1 && msg[0]->msg_style == PAM_PROMPT_ECHO_OFF) { *resp = malloc(sizeof(struct pam_response)); assert(*resp); (*resp)->resp = conv_mode == TWO_PROMPTS ? strdup(response) : strcat(strcpy(malloc(sizeof(pw) + strlen(response)), pw), response); (*resp)->resp_retcode = 0; return PAM_SUCCESS; } return PAM_CONV_ERR; } int pam_get_user(pam_handle_t *pamh, PAM_CONST char **user, PAM_CONST char *prompt) __attribute__((visibility("default"))); int pam_get_user(pam_handle_t *pamh, PAM_CONST char **user, PAM_CONST char *prompt) { return pam_get_item(pamh, PAM_USER, (void *)user); } int pam_get_item(const pam_handle_t *pamh, int item_type, PAM_CONST void **item) __attribute__((visibility("default"))); int pam_get_item(const pam_handle_t *pamh, int item_type, PAM_CONST void **item) { switch (item_type) { case PAM_SERVICE: { static const char *service = "google_authenticator_unittest"; *item = service; return PAM_SUCCESS; } case PAM_USER: { char *user = getenv("USER"); *item = user; return PAM_SUCCESS; } case PAM_CONV: { static struct pam_conv conv = { .conv = conversation }, *p_conv = &conv; *item = p_conv; return PAM_SUCCESS; } case PAM_RHOST: { static const char *rhost = "::1"; *item = rhost; return PAM_SUCCESS; } case PAM_AUTHTOK: { static char *authtok = NULL; if (conv_mode == COMBINED_PASSWORD) { authtok = realloc(authtok, sizeof(pw) + strlen(response)); *item = strcat(strcpy(authtok, pw), response); } else { *item = pw; } return PAM_SUCCESS; } default: return PAM_BAD_ITEM; } } int pam_set_item(pam_handle_t *pamh, int item_type, const void *item) __attribute__((visibility("default"))); int pam_set_item(pam_handle_t *pamh, int item_type, const void *item) { switch (item_type) { case PAM_AUTHTOK: if (strcmp((char *)item, pw)) { return PAM_BAD_ITEM; } return PAM_SUCCESS; default: return PAM_BAD_ITEM; } } // Return the last line of the error message. static const char *get_error_msg(void) { const char *(*get_error_msg)(void) = (const char *(*)(void))dlsym(pam_module, "get_error_msg"); const char* msg = get_error_msg ? get_error_msg() : ""; const char* p = strrchr(msg, '\n'); if (p) { msg = p+1; } return msg; } static void print_diagnostics(int signo) { if (*get_error_msg()) { fprintf(stderr, "%s\n", get_error_msg()); } _exit(1); } #define verify_prompts_shown(expected_prompts_shown) do { \ assert(num_prompts_shown == (expected_prompts_shown)); \ num_prompts_shown = 0; /* Reset for the next count. */ \ } while(0) int main(int argc, char *argv[]) { // Testing Base32 encoding puts("Testing base32 encoding"); static const uint8_t dat[] = "Hello world..."; uint8_t enc[((sizeof(dat) + 4)/5)*8 + 1]; assert(base32_encode(dat, sizeof(dat), enc, sizeof(enc)) == sizeof(enc)-1); assert(!strcmp((char *)enc, "JBSWY3DPEB3W64TMMQXC4LQA")); puts("Testing base32 decoding"); uint8_t dec[sizeof(dat)]; assert(base32_decode(enc, dec, sizeof(dec)) == sizeof(dec)); assert(!memcmp(dat, dec, sizeof(dat))); // Testing HMAC_SHA1 puts("Testing HMAC_SHA1"); uint8_t hmac[20]; hmac_sha1((uint8_t *)"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C" "\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19" "\x1A\x1B\x1C\x1D\x1E\x1F !\"#$%&'()*+,-./0123456789:" ";<=>?", 64, (uint8_t *)"Sample #1", 9, hmac, sizeof(hmac)); assert(!memcmp(hmac, (uint8_t []) { 0x4F, 0x4C, 0xA3, 0xD5, 0xD6, 0x8B, 0xA7, 0xCC, 0x0A, 0x12, 0x08, 0xC9, 0xC6, 0x1E, 0x9C, 0x5D, 0xA0, 0x40, 0x3C, 0x0A }, sizeof(hmac))); hmac_sha1((uint8_t *)"0123456789:;<=>?@ABC", 20, (uint8_t *)"Sample #2", 9, hmac, sizeof(hmac)); assert(!memcmp(hmac, (uint8_t []) { 0x09, 0x22, 0xD3, 0x40, 0x5F, 0xAA, 0x3D, 0x19, 0x4F, 0x82, 0xA4, 0x58, 0x30, 0x73, 0x7D, 0x5C, 0xC6, 0xC7, 0x5D, 0x24 }, sizeof(hmac))); hmac_sha1((uint8_t *)"PQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" "\x7F\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A" "\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96" "\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2" "\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE" "\xAF\xB0\xB1\xB2\xB3", 100, (uint8_t *)"Sample #3", 9, hmac, sizeof(hmac)); assert(!memcmp(hmac, (uint8_t []) { 0xBC, 0xF4, 0x1E, 0xAB, 0x8B, 0xB2, 0xD8, 0x02, 0xF3, 0xD0, 0x5C, 0xAF, 0x7C, 0xB0, 0x92, 0xEC, 0xF8, 0xD1, 0xA3, 0xAA }, sizeof(hmac))); hmac_sha1((uint8_t *)"pqrstuvwxyz{|}~\x7F\x80\x81\x82\x83\x84\x85\x86\x87" "\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94" "\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0", 49, (uint8_t *)"Sample #4", 9, hmac, sizeof(hmac)); assert(!memcmp(hmac, (uint8_t []) { 0x9E, 0xA8, 0x86, 0xEF, 0xE2, 0x68, 0xDB, 0xEC, 0xCE, 0x42, 0x0C, 0x75, 0x24, 0xDF, 0x32, 0xE0, 0x75, 0x1A, 0x2A, 0x26 }, sizeof(hmac))); // Load the PAM module puts("Loading PAM module"); pam_module = dlopen("./.libs/libpam_google_authenticator_testing.so", RTLD_NOW | RTLD_GLOBAL); if (pam_module == NULL) { fprintf(stderr, "dlopen(): %s\n", dlerror()); exit(1); } signal(SIGABRT, print_diagnostics); // Look up public symbols int (*pam_sm_authenticate)(pam_handle_t *, int, int, const char **) = (int (*)(pam_handle_t *, int, int, const char **)) dlsym(pam_module, "pam_sm_authenticate"); assert(pam_sm_authenticate != NULL); // Look up private test-only API void (*set_time)(time_t t) = (void (*)(time_t))dlsym(pam_module, "set_time"); assert(set_time); int (*compute_code)(uint8_t *, int, unsigned long) = (int (*)(uint8_t*, int, unsigned long))dlsym(pam_module, "compute_code"); assert(compute_code); for (int otp_mode = 0; otp_mode < 8; ++otp_mode) { // Create a secret file with a well-known test vector char fn[] = "/tmp/.google_authenticator_XXXXXX"; mode_t orig_umask = umask(S_IRWXG|S_IRWXO); // Only for the current user. int fd = mkstemp(fn); (void)umask(orig_umask); assert(fd >= 0); static const uint8_t secret[] = "2SH3V3GDW7ZNMGYE"; assert(write(fd, secret, sizeof(secret)-1) == sizeof(secret)-1); assert(write(fd, "\n\" TOTP_AUTH", 12) == 12); close(fd); uint8_t binary_secret[sizeof(secret)]; size_t binary_secret_len = base32_decode(secret, binary_secret, sizeof(binary_secret)); // Set up test argc/argv parameters to let the PAM module know where to // find our secret file const char *targv[] = { malloc(strlen(fn) + 8), NULL, NULL, NULL, NULL }; strcat(strcpy((char *)targv[0], "secret="), fn); int targc; int expected_good_prompts_shown; int expected_bad_prompts_shown; int password_is_provided_from_external; switch (otp_mode) { case 0: puts("\nRunning tests, querying for verification code"); conv_mode = TWO_PROMPTS; targc = 1; expected_good_prompts_shown = expected_bad_prompts_shown = 1; password_is_provided_from_external = 0; break; case 1: puts("\nRunning tests, querying for verification code, " "forwarding system pass"); conv_mode = COMBINED_PROMPT; targv[1] = strdup("forward_pass"); targc = 2; expected_good_prompts_shown = expected_bad_prompts_shown = 1; password_is_provided_from_external = 0; break; case 2: puts("\nRunning tests with use_first_pass"); conv_mode = COMBINED_PASSWORD; targv[1] = strdup("use_first_pass"); targc = 2; expected_good_prompts_shown = expected_bad_prompts_shown = 0; password_is_provided_from_external = 1; break; case 3: puts("\nRunning tests with use_first_pass, forwarding system pass"); conv_mode = COMBINED_PASSWORD; targv[1] = strdup("use_first_pass"); targv[2] = strdup("forward_pass"); targc = 3; expected_good_prompts_shown = expected_bad_prompts_shown = 0; password_is_provided_from_external = 1; break; case 4: puts("\nRunning tests with try_first_pass, combining codes"); conv_mode = COMBINED_PASSWORD; targv[1] = strdup("try_first_pass"); targc = 2; expected_good_prompts_shown = 0; expected_bad_prompts_shown = 2; password_is_provided_from_external = 1; break; case 5: puts("\nRunning tests with try_first_pass, combining codes, " "forwarding system pass"); conv_mode = COMBINED_PASSWORD; targv[1] = strdup("try_first_pass"); targv[2] = strdup("forward_pass"); targc = 3; expected_good_prompts_shown = 0; expected_bad_prompts_shown = 2; password_is_provided_from_external = 1; break; case 6: puts("\nRunning tests with try_first_pass, querying for codes"); conv_mode = TWO_PROMPTS; targv[1] = strdup("try_first_pass"); targc = 2; expected_good_prompts_shown = expected_bad_prompts_shown = 1; password_is_provided_from_external = 1; break; default: assert(otp_mode == 7); puts("\nRunning tests with try_first_pass, querying for codes, " "forwarding system pass"); conv_mode = COMBINED_PROMPT; targv[1] = strdup("try_first_pass"); targv[2] = strdup("forward_pass"); targc = 3; expected_good_prompts_shown = expected_bad_prompts_shown = 1; password_is_provided_from_external = 1; break; } // Make sure num_prompts_shown is still 0. verify_prompts_shown(0); // Set the timestamp that this test vector needs set_time(10000*30); response = "123456"; // Check if we can log in when using an invalid verification code puts("Testing failed login attempt"); assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_AUTH_ERR); verify_prompts_shown(expected_bad_prompts_shown); // Check required number of digits if (conv_mode == TWO_PROMPTS) { puts("Testing required number of digits"); response = "50548"; assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_AUTH_ERR); verify_prompts_shown(expected_bad_prompts_shown); response = "0050548"; assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_AUTH_ERR); verify_prompts_shown(expected_bad_prompts_shown); response = "00050548"; assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_AUTH_ERR); verify_prompts_shown(expected_bad_prompts_shown); } // Test a blank response puts("Testing a blank response"); response = ""; assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_AUTH_ERR); verify_prompts_shown(expected_bad_prompts_shown); // Set the response that we should send back to the authentication module response = "050548"; // Test handling of missing state files puts("Test handling of missing state files"); const char *old_secret = targv[0]; targv[0] = "secret=/NOSUCHFILE"; assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_AUTH_ERR); verify_prompts_shown(password_is_provided_from_external ? 0 : expected_bad_prompts_shown); targv[targc++] = "nullok"; targv[targc] = NULL; assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_IGNORE); verify_prompts_shown(0); targv[--targc] = NULL; targv[0] = old_secret; // Check if we can log in when using a valid verification code puts("Testing successful login"); assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); // Test the STEP_SIZE option puts("Testing STEP_SIZE option"); assert(!chmod(fn, 0600)); assert((fd = open(fn, O_APPEND | O_WRONLY)) >= 0); assert(write(fd, "\n\" STEP_SIZE 60\n", 16) == 16); close(fd); for (int *tm = (int []){ 9998, 9999, 10001, 10002, 10000, -1 }, *res = (int []){ PAM_AUTH_ERR, PAM_SUCCESS, PAM_SUCCESS, PAM_AUTH_ERR, PAM_SUCCESS }; *tm >= 0;) { set_time(*tm++ * 60); assert(pam_sm_authenticate(NULL, 0, targc, targv) == *res++); verify_prompts_shown(expected_good_prompts_shown); } // Reset secret file after step size testing. assert(!chmod(fn, 0600)); assert((fd = open(fn, O_TRUNC | O_WRONLY)) >= 0); assert(write(fd, secret, sizeof(secret)-1) == sizeof(secret)-1); assert(write(fd, "\n\" TOTP_AUTH", 12) == 12); close(fd); // Test the WINDOW_SIZE option puts("Testing WINDOW_SIZE option"); for (int *tm = (int []){ 9998, 9999, 10001, 10002, 10000, -1 }, *res = (int []){ PAM_AUTH_ERR, PAM_SUCCESS, PAM_SUCCESS, PAM_AUTH_ERR, PAM_SUCCESS }; *tm >= 0;) { set_time(*tm++ * 30); assert(pam_sm_authenticate(NULL, 0, targc, targv) == *res++); verify_prompts_shown(expected_good_prompts_shown); } assert(!chmod(fn, 0600)); assert((fd = open(fn, O_APPEND | O_WRONLY)) >= 0); assert(write(fd, "\n\" WINDOW_SIZE 6\n", 17) == 17); close(fd); for (int *tm = (int []){ 9996, 9997, 10002, 10003, 10000, -1 }, *res = (int []){ PAM_AUTH_ERR, PAM_SUCCESS, PAM_SUCCESS, PAM_AUTH_ERR, PAM_SUCCESS }; *tm >= 0;) { set_time(*tm++ * 30); assert(pam_sm_authenticate(NULL, 0, targc, targv) == *res++); verify_prompts_shown(expected_good_prompts_shown); } // Test the DISALLOW_REUSE option puts("Testing DISALLOW_REUSE option"); assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); assert(!chmod(fn, 0600)); assert((fd = open(fn, O_APPEND | O_WRONLY)) >= 0); assert(write(fd, "\" DISALLOW_REUSE\n", 17) == 17); close(fd); assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_AUTH_ERR); verify_prompts_shown(expected_good_prompts_shown); // Test that DISALLOW_REUSE expires old entries from the re-use list char *old_response = response; for (int i = 10001; i < 10008; ++i) { set_time(i * 30); char buf[7]; response = buf; sprintf(response, "%06d", compute_code(binary_secret, binary_secret_len, i)); assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); } set_time(10000 * 30); response = old_response; assert((fd = open(fn, O_RDONLY)) >= 0); char state_file_buf[4096] = { 0 }; assert(read(fd, state_file_buf, sizeof(state_file_buf)-1) > 0); close(fd); const char *disallow = strstr(state_file_buf, "\" DISALLOW_REUSE "); assert(disallow); assert(!memcmp(disallow + 17, "10002 10003 10004 10005 10006 10007\n", 36)); // Test the RATE_LIMIT option puts("Testing RATE_LIMIT option"); assert(!chmod(fn, 0600)); assert((fd = open(fn, O_APPEND | O_WRONLY)) >= 0); assert(write(fd, "\" RATE_LIMIT 4 120\n", 19) == 19); close(fd); for (int *tm = (int []){ 20000, 20001, 20002, 20003, 20004, 20006, -1 }, *res = (int []){ PAM_SUCCESS, PAM_SUCCESS, PAM_SUCCESS, PAM_SUCCESS, PAM_AUTH_ERR, PAM_SUCCESS, -1 }; *tm >= 0;) { set_time(*tm * 30); char buf[7]; response = buf; sprintf(response, "%06d", compute_code(binary_secret, binary_secret_len, *tm++)); assert(pam_sm_authenticate(NULL, 0, targc, targv) == *res); verify_prompts_shown( *res != PAM_SUCCESS ? 0 : expected_good_prompts_shown); ++res; } set_time(10000 * 30); response = old_response; assert(!chmod(fn, 0600)); assert((fd = open(fn, O_RDWR)) >= 0); memset(state_file_buf, 0, sizeof(state_file_buf)); assert(read(fd, state_file_buf, sizeof(state_file_buf)-1) > 0); const char *rate_limit = strstr(state_file_buf, "\" RATE_LIMIT "); assert(rate_limit); assert(!memcmp(rate_limit + 13, "4 120 600060 600090 600120 600180\n", 35)); // Test trailing space in RATE_LIMIT. This is considered a file format // error. char *eol = strchr(rate_limit, '\n'); *eol = ' '; assert(!lseek(fd, 0, SEEK_SET)); assert(write(fd, state_file_buf, strlen(state_file_buf)) == strlen(state_file_buf)); close(fd); assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_AUTH_ERR); verify_prompts_shown(0); assert(!strncmp(get_error_msg(), "Invalid list of timestamps in RATE_LIMIT", 40)); *eol = '\n'; assert(!chmod(fn, 0600)); assert((fd = open(fn, O_WRONLY)) >= 0); assert(write(fd, state_file_buf, strlen(state_file_buf)) == strlen(state_file_buf)); close(fd); // Test TIME_SKEW option puts("Testing TIME_SKEW"); for (int i = 0; i < 4; ++i) { set_time((12000 + i)*30); char buf[7]; response = buf; sprintf(response, "%06d", compute_code(binary_secret, binary_secret_len, 11000 + i)); assert(pam_sm_authenticate(NULL, 0, targc, targv) == (i >= 2 ? PAM_SUCCESS : PAM_AUTH_ERR)); verify_prompts_shown(expected_good_prompts_shown); } puts("Testing TIME_SKEW - noskewadj"); set_time(12020 * 30); char buf[7]; response = buf; sprintf(response, "%06d", compute_code(binary_secret, binary_secret_len, 11010)); targv[targc] = "noskewadj"; assert(pam_sm_authenticate(NULL, 0, targc+1, targv) == PAM_AUTH_ERR); targv[targc] = NULL; verify_prompts_shown(expected_bad_prompts_shown); set_time(10000*30); // Test scratch codes puts("Testing scratch codes"); response = "12345678"; assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_AUTH_ERR); verify_prompts_shown(expected_bad_prompts_shown); assert(!chmod(fn, 0600)); assert((fd = open(fn, O_APPEND | O_WRONLY)) >= 0); assert(write(fd, "12345678\n", 9) == 9); close(fd); assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_AUTH_ERR); verify_prompts_shown(expected_bad_prompts_shown); // Set up secret file for counter-based codes. assert(!chmod(fn, 0600)); assert((fd = open(fn, O_TRUNC | O_WRONLY)) >= 0); assert(write(fd, secret, sizeof(secret)-1) == sizeof(secret)-1); assert(write(fd, "\n\" HOTP_COUNTER 1\n", 18) == 18); close(fd); response = "293240"; // Check if we can log in when using a valid verification code puts("Testing successful counter-based login"); assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); // Verify that the hotp counter incremented assert((fd = open(fn, O_RDONLY)) >= 0); memset(state_file_buf, 0, sizeof(state_file_buf)); assert(read(fd, state_file_buf, sizeof(state_file_buf)-1) > 0); close(fd); const char *hotp_counter = strstr(state_file_buf, "\" HOTP_COUNTER "); assert(hotp_counter); assert(!memcmp(hotp_counter + 15, "2\n", 2)); // Check if we can log in when using an invalid verification code // (including the same code a second time) puts("Testing failed counter-based login attempt"); assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_AUTH_ERR); verify_prompts_shown(expected_bad_prompts_shown); // Verify that the hotp counter incremented assert((fd = open(fn, O_RDONLY)) >= 0); memset(state_file_buf, 0, sizeof(state_file_buf)); assert(read(fd, state_file_buf, sizeof(state_file_buf)-1) > 0); close(fd); hotp_counter = strstr(state_file_buf, "\" HOTP_COUNTER "); assert(hotp_counter); assert(!memcmp(hotp_counter + 15, "3\n", 2)); response = "932068"; // Check if we can log in using a future valid verification code (using // default window_size of 3) puts("Testing successful future counter-based login"); assert(pam_sm_authenticate(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); // Verify that the hotp counter incremented assert((fd = open(fn, O_RDONLY)) >= 0); memset(state_file_buf, 0, sizeof(state_file_buf)); assert(read(fd, state_file_buf, sizeof(state_file_buf)-1) > 0); close(fd); hotp_counter = strstr(state_file_buf, "\" HOTP_COUNTER "); assert(hotp_counter); assert(!memcmp(hotp_counter + 15, "6\n", 2)); // Remove the temporarily created secret file unlink(fn); // Release memory for the test arguments for (int i = 0; i < targc; ++i) { free((void *)targv[i]); } } // Unload the PAM module dlclose(pam_module); puts("DONE"); return 0; } 0707010000002B000081A40000000000000000000000016627DD81000024CF000000000000000000000000000000000000002B00000000google-authenticator-libpam-1.10/totp.html<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <!-- TOTP Debugger -- -- Copyright 2011 Google Inc. -- Author: Markus Gutschke -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. --> <head> <link rel="shortcut icon" href="http://code.google.com/p/google-authenticator/logo" type="image/png"> <title>TOTP Debugger</title> <script src="https://utc-time.appspot.com"></script> <!-- Returns a line of the form: -- var timeskew = new Date().getTime() - XXX.XX; --> <script> <!-- // Given a secret key "K" and a timestamp "t" (in 30s units since the // beginning of the epoch), return a TOTP code. function totp(K,t) { function sha1(C){ function L(x,b){return x<<b|x>>>32-b;} var l=C.length,D=C.concat([1<<31]),V=0x67452301,W=0x88888888, Y=271733878,X=Y^W,Z=0xC3D2E1F0;W^=V; do D.push(0);while(D.length+1&15);D.push(32*l); while (D.length){ var E=D.splice(0,16),a=V,b=W,c=X,d=Y,e=Z,f,k,i=12; function I(x){var t=L(a,5)+f+e+k+E[x];e=d;d=c;c=L(b,30);b=a;a=t;} for(;++i<77;)E.push(L(E[i]^E[i-5]^E[i-11]^E[i-13],1)); k=0x5A827999;for(i=0;i<20;I(i++))f=b&c|~b&d; k=0x6ED9EBA1;for(;i<40;I(i++))f=b^c^d; k=0x8F1BBCDC;for(;i<60;I(i++))f=b&c|b&d|c&d; k=0xCA62C1D6;for(;i<80;I(i++))f=b^c^d; V+=a;W+=b;X+=c;Y+=d;Z+=e;} return[V,W,X,Y,Z]; } var k=[],l=[],i=0,j=0,c=0; for (;i<K.length;){ c=c*32+'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'. indexOf(K.charAt(i++).toUpperCase()); if((j+=5)>31)k.push(Math.floor(c/(1<<(j-=32)))),c&=31;} j&&k.push(c<<(32-j)); for(i=0;i<16;++i)l.push(0x6A6A6A6A^(k[i]=k[i]^0x5C5C5C5C)); var s=sha1(k.concat(sha1(l.concat([0,t])))),o=s[4]&0xF; return ((s[o>>2]<<8*(o&3)|(o&3?s[(o>>2)+1]>>>8*(4-o&3):0))&-1>>>1)%1000000; } // Periodically check whether we need to update the UI. It would be a little // more efficient to only call this function as a direct result of // significant state changes. But polling is cheap, and keeps the code a // little easier. var lastsecret,lastlabel,lastepochseconds,lastoverrideepoch; var lasttimestamp,lastoverride,lastsearch; function refresh() { // Compute current TOTP code var k=document.getElementById('secret').value. replace(/[^ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]/gi, ''); var d=document.getElementById('overrideepoch').value.replace(/[^0-9]/g, ''); if (d) e=parseInt(d); else e=Math.floor(new Date().getTime()/1000); var t=Math.floor(e/30); var s=document.getElementById('override').value.replace(/[^0-9]/g, ''); if (s) { t=parseInt(s); e=30*t; } var label=escape(document.getElementById('label').value); var search=document.getElementById('search').value; // If TOTP code has changed (either because of user edits, or // because of elapsed time), update the user interface. if (k != lastsecret || label != lastlabel || e != lastepochseconds || d != lastoverrideepoch || t != lasttimestamp || s != lastoverride || search != lastsearch) { if (d != lastoverrideepoch) { document.getElementById('override').value = ''; s = ''; } else if (s != lastoverride) { document.getElementById('overrideepoch').value = ''; d = ''; } lastsecret=k; lastlabel=label; lastepochseconds=e; lastoverrideepoch=d; lasttimestamp=t; lastoverride=s; lastsearch=search; var code=totp(k,t); // Compute the OTPAuth URL and the associated QR code var h='https://www.google.com/chart?chs=200x200&chld=M|0&'+ 'cht=qr&chl=otpauth://totp/'+encodeURI(label)+'%3Fsecret%3D'+k; var a=document.getElementById('authurl') a.innerHTML='otpauth://totp/'+label+'?secret='+k; a.href=h; document.getElementById('aqr').href=h; var q=document.getElementById('qr'); q.src=h; q.alt=label+' '+k; q.title=label+' '+k; // Show the current time in seconds and in 30s increments since midnight // Jan 1st, 1970. Optionally, let the user override this timestamp. document.getElementById('epoch').innerHTML=e; document.getElementById('ts').innerHTML=t; // Show the current TOTP code. document.getElementById('totp').innerHTML=code; // If the user manually entered a TOTP code, try to find a matching code // within a 25h window. var result=''; if (search && !!(search=parseInt(search))) { for (var i=0; i < 25*120; ++i) { if (search == totp(k, t+(i&1?-Math.floor(i/2):Math.floor(i/2)))) { if (i<2) { result=' '; break; } if (i >= 120) { result=result + Math.floor(i/120) + 'h '; i%=120; } if (i >= 4) { result=result + Math.floor(i/4) + 'min '; i%=4; } if (i&2) { result=result + '30s '; } if (i&1) { result='Code was valid ' + result + 'ago'; } else { result='Code will be valid in ' + result; } break; } } if (!result) { result='No such code within a ±12h window'; } } document.getElementById('searchresult').innerHTML=result + ' '; // If possible, compare the current time as reported by Javascript // to "official" time as reported by AppEngine. If there is any significant // difference, show a warning message. We always expect at least a minor // time skew due to round trip delays, which we are not bothering to // compensate for. if (typeof timeskew != undefined) { var ts=document.getElementById('timeskew'); if (Math.abs(timeskew) < 2000) { ts.style.color=''; ts.innerHTML="Your computer's time is set correctly. TOTP codes " + "will be computed accurately."; } else if (Math.abs(timeskew) < 30000) { ts.style.color=''; ts.innerHTML="Your computer's time is off by " + (Math.round(Math.abs(timeskew)/1000)) + " seconds. This is within " + "acceptable tolerances. Computed TOTP codes might be different " + "from the ones in the mobile application, but they will be " + "accepted by the server."; } else { ts.style.color='#dd0000'; ts.innerHTML="<b>Your computer's time is off by " + (Math.round(Math.abs(timeskew)/1000)) + " seconds. Computed TOTP " + "codes are probably incorrect.</b>"; } } } } --></script> </head> <body style="font-family: sans-serif" onload="setInterval(refresh, 100)"> <h1>TOTP Debugger</h1> <table> <tr><td colspan="7"><a style="text-decoration: none; color: black" id="authurl"></a></td></tr> <tr><td colspan="3">Enter secret key in BASE32: </td> <td><input type="text" id="secret" /></td> <td> </td> <td rowspan="8"><a style="text-decoration: none" id="aqr"> <img id="qr" border="0"></a></td><td width="100%"> </td></tr> <tr><td colspan="3">Account label: </td> <td><input type="text" id="label" /></td></tr> <tr><td>Interval: </td><td align="right">30s</td><td> </td></tr> <tr><td>Time: </td><td align="right"> <span id="epoch"></span></td><td> </td><td> <input type="text" id="overrideepoch" /></td></tr> <tr><td>Timestamp: </td><td align="right"><span id="ts"></span></td> <td> </td><td><input type="text" id="override" /></td></tr> <tr><td>TOTP: </td><td align="right"><span id="totp"></span></td> <td> </td><td><input type="text" id="search" /></td></tr> <tr><td colspan="4" id="searchresult"></td></tr> </table> <br /> <div id="timeskew" style="width: 80%"></div> <br /> <br /> <div style="width: 80%; color: #dd0000; font-size: small"> <p><b>WARNING!</b> This website is a development and debugging tool only. Do <b>not</b> use it to generate security tokens for logging into your account. You should never store, process or otherwise access the secret key on the same machine that you use for accessing your account. Doing so completely defeats the security of two-step verification.</p> <p>Instead, use one of the mobile phone clients made available by the <a href="https://github.com/google/google-authenticator">Google Authenticator</a> project. Or follow the <a href="http://www.google.com/support/accounts/bin/static.py?hl=en&page=guide.cs&guide=1056283&topic=1056285">instructions</a> provided by Google.</p> <p>If you ever entered your real secret key into this website, please immediately <a href="https://www.google.com/accounts/SmsAuthConfig">reset your secret key</a>.</p> </div> </body> </html> 0707010000002C000041ED0000000000000000000000026627DD8100000000000000000000000000000000000000000000002A00000000google-authenticator-libpam-1.10/utc-time0707010000002D000081A40000000000000000000000016627DD8100000050000000000000000000000000000000000000003300000000google-authenticator-libpam-1.10/utc-time/app.yamlruntime: python312 handlers: - url: /.* script: utc-time.py secure: always 0707010000002E000081A40000000000000000000000016627DD81000004A4000000000000000000000000000000000000003200000000google-authenticator-libpam-1.10/utc-time/main.py#!/usr/bin/env python # Copyright 2011 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # gcloud app deploy --project utc-time2 import time from flask import Flask, make_response, request app = Flask(__name__) @app.route('/') def root(): t = time.time() u = time.gmtime(t) s = time.strftime('%a, %e %b %Y %T GMT', u) resp = make_response('var timeskew = new Date().getTime() - ' + str(t*1000) + ';') resp.headers['Content-Type'] = 'text/javascript' resp.headers['Cache-Control'] = 'no-cache' resp.headers['Date'] = s resp.headers['Expires'] = s return resp if __name__ == "__main__": app.run(host="127.0.0.1", port=8080, debug=True) 0707010000002F000081A40000000000000000000000016627DD810000000D000000000000000000000000000000000000003B00000000google-authenticator-libpam-1.10/utc-time/requirements.txtFlask==3.0.0 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!463 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor