├── .gitignore ├── composer.json ├── test.local.sh ├── pam_script_auth ├── php.ini ├── LICENSE ├── test.sh ├── .github └── workflows │ └── test.yml ├── README.md └── pam-script-saml.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | composer.phar 3 | test.env 4 | /vendor/ 5 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "lightsaml/lightsaml": "^1.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test.local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 3 | 4 | for PHP_PATH in /Applications/MAMP/bin/php/php*/bin; do 5 | echo "********************************************************************************" 6 | echo "PHP: ${PHP_PATH}" 7 | PATH="${PHP_PATH}:${PATH}" "${DIR}/test.sh" 8 | done 9 | -------------------------------------------------------------------------------- /pam_script_auth: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # set path for verbose logging 4 | LOGFILE= 5 | #LOGFILE=/tmp/pam-script-saml.log 6 | 7 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 8 | OUTPUT=$(/usr/bin/env php -c "$DIR/php.ini" -f "$DIR/pam-script-saml.php" "$@") 9 | RETCODE=$? 10 | 11 | if [[ -n "$LOGFILE" ]]; then 12 | echo "[$(/usr/bin/env date -R)] $OUTPUT" >> $LOGFILE 13 | fi 14 | 15 | exit $RETCODE 16 | -------------------------------------------------------------------------------- /php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | engine = On 3 | short_open_tag = Off 4 | zend.enable_gc = Off 5 | memory_limit = 4M 6 | error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT 7 | display_errors = On 8 | display_startup_errors = On 9 | log_errors = On 10 | register_argc_argv = On 11 | enable_post_data_reading = Off 12 | default_mimetype = "" 13 | enable_dl = Off 14 | file_uploads = Off 15 | 16 | [Date] 17 | date.timezone = "Europe/Berlin" 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Christoph Kreutzer 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 3 | 4 | echo "================================================================================" 5 | if [[ -f "${DIR}/test.env" ]]; then 6 | echo "Loading test environment data from: ${DIR}/test.env" 7 | . "${DIR}/test.env" 8 | fi 9 | 10 | echo "Checking test environment data..." 11 | if [[ -z "$ITERATIONS" || -z "$IDP_METADATA" || -z "$TRUSTED_SP" || 12 | -z "$PAM_AUTHTOK" || -z "$PAM_RHOST" || -z "$PAM_TYPE" || 13 | -z "$PAM_USER" ]]; then 14 | echo "Failed!" 15 | exit 2 16 | fi 17 | echo "Succeeded." 18 | 19 | IDP_METADATA_FILE=$(mktemp) 20 | echo "$IDP_METADATA" | tr -d '\r' > "${IDP_METADATA_FILE}" 21 | 22 | echo "--------------------------------------------------------------------------------" 23 | php -v 24 | echo "--------------------------------------------------------------------------------" 25 | RC=0 26 | START=$(date +%s) 27 | 28 | for ((i=1; i<=ITERATIONS; i++)); do 29 | php -c "${DIR}/php.ini" \ 30 | -f "${DIR}/pam-script-saml.php" \ 31 | userid=mail \ 32 | idp="${IDP_METADATA_FILE}" \ 33 | trusted_sp="${TRUSTED_SP}" \ 34 | grace=2147483647 \ 35 | only_from=127.0.0.1,::1 36 | RC=$? 37 | if [[ $RC -ne 0 ]]; then 38 | echo "An error occured in the test, aborting." 39 | break 40 | fi 41 | done 42 | 43 | END=$(date +%s) 44 | if [[ $RC -eq 0 ]]; then 45 | echo "Duration for $ITERATIONS iterations: $(( END-START ))s" 46 | fi 47 | echo "================================================================================" 48 | 49 | rm -f "${IDP_METADATA_FILE}" 50 | 51 | exit $RC 52 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test functionality and compatibility 2 | 3 | on: 4 | # run on push 5 | push: 6 | branches: [ master ] 7 | # and once every month 8 | schedule: 9 | - cron: '07 16 13 * *' 10 | jobs: 11 | run: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | operating-system: ['ubuntu-latest'] 16 | php-versions: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] 17 | runs-on: ${{ matrix.operating-system }} 18 | name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v2 22 | 23 | - name: Setup PHP 24 | uses: shivammathur/setup-php@v2 25 | with: 26 | php-version: ${{ matrix.php-versions }} 27 | extensions: mbstring, xml, dom, :opcache 28 | coverage: none 29 | env: 30 | COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | - name: PHP information 33 | run: | 34 | php -v 35 | php -m 36 | 37 | - name: Install Composer dependencies 38 | run: composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader 39 | 40 | - name: Show Composer dependencies 41 | run: composer show --tree --no-interaction 42 | 43 | - name: Run test 44 | run: bash ./test.sh 45 | env: 46 | ITERATIONS: 500 47 | IDP_METADATA: ${{ secrets.TEST_IDP_METADATA }} 48 | TRUSTED_SP: ${{ secrets.TEST_TRUSTED_SP }} 49 | PAM_AUTHTOK: ${{ secrets.TEST_PAM_AUTHTOK }} 50 | PAM_RHOST: ${{ secrets.TEST_PAM_RHOST }} 51 | PAM_TYPE: ${{ secrets.TEST_PAM_TYPE }} 52 | PAM_USER: ${{ secrets.TEST_PAM_USER }} 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pam-script-saml 2 | 3 | This is a PAM module (using pam_script) which validates SAML assertions given as password. It is inspired by crudesaml, but implemented in PHP using LightSAML Core library. 4 | 5 | Currently (and probably definately) only the `auth` PAM type is supported. For all other types you usually want to use another module (in the simplest case e.g. `pam_permit.so`). 6 | 7 | License: [BSD 2-Clause](LICENSE) 8 | 9 | Inspired by [crudesaml](https://ftp.espci.fr/pub/crudesaml/), but doesn't depend on (a patched) liblasso3. 10 | 11 | ## Key features 12 | * Verification of SAML2 assertions as password replacement 13 | * configuration options similar to crudesaml 14 | 15 | ## Compatibility 16 | Integrates well with [SOGo Groupware](https://sogo.nu/) and the [Dovecot MDA](http://dovecot.org/) using PAM authentication. 17 | 18 | ## Configuration options 19 | Passed in the PAM configuration in the format `key=value` (analog to crudesaml). 20 | 21 | * `userid`: name of SAML attribute which contains the username. The value will be matched against the username passed by PAM. Default: `uid` 22 | * `grace`: Time frame (in seconds) allowing the validation of the assertion deviating from the given time frame in the assertion (for clock skew or longer authentication validity). Default: `600` 23 | * `saml_check_timeframe`: If `0` (disabled), validates the assertion also when it's expired. Default: `1` 24 | * `idp`: Path to metadata file from which IdP certificates for assertion signature validation are extracted (multiple allowed). Signature is not verified, if none is given (not recommended!). 25 | * `trusted_sp`: EntityID of SP which should be trusted (i.e. which is in the Audience {Assertion/Conditions/AudienceRestriction/Audience}). All are allowed, if none is given (not recommended!). 26 | * `only_from`: Comma-separated list of IPs which can authenticate. 27 | 28 | Logging can be enabled by using the `pam_script_auth` wrapper script and setting the `LOGFILE` variable. This helps troubleshooting a lot, since pam-script-saml is indicating where the validation fails. 29 | 30 | ## Installation 31 | 1. Download: 32 | 1. Clone via git: `git clone https://github.com/ck-ws/pam-script-saml.git` 33 | 2. Zipball: `https://github.com/ck-ws/pam-script-saml/archive/master.zip` 34 | 2. Install dependencies: `composer.phar install` 35 | 3. Make sure the following PHP extensions are installed: dom, mbstring, mcrypt, opcache (zend_extension) 36 | 4. Configure (see below) 37 | 38 | ## Configuration 39 | 1. Install [pam_script](https://github.com/jeroennijhof/pam_script) from source or from your distribution. 40 | 2. Install `pam-script-saml` in a directory of your choice (see above). 41 | 3. Use the given `pam_script_auth` file (or create a symlink from `pam_script_auth` to `pam-script-saml.php`) 42 | 4. configure the PAM module in `/etc/pam.d/` like this, for example: 43 | ```` 44 | auth required pam_script.so dir=