pax_global_header00006660000000000000000000000064133611424410014511gustar00rootroot0000000000000052 comment=b305dff5581e2b0a44ec7f622d18980cc0e67180 mod_auth_pubtkt-0.13/000077500000000000000000000000001336114244100146255ustar00rootroot00000000000000mod_auth_pubtkt-0.13/CHANGELOG000066400000000000000000000066611336114244100160500ustar00rootroot00000000000000v0.13 (2018-10-15) ------------------ - Add compatibility with OpenSSL 1.1 API (Contributed by Vulpeculus) v0.12 (2018-03-15) ------------------ - Add TKTAuthRequireMultifactor and TKTAuthMultifactorURL. These can be used to protect certain Directory/Location directives with an additional login factor to achieve multifactor. Like the original login, the multifactor method is left up to the ticket generation application, only requiring an attestation that multifactor has been supplied. (Contributed by Nick Ramser) v0.11 (2017-02-28) ------------------ - Fixes selection of digest algorithm when using TKTAuthDigest. v0.10 (2016-12-16) ------------------ - New option TKTAuthDigest allowing selection of the digest algorithm. If not configured, the old defaults of SHA1 (for RSA privkey) and DSS1 (for DSA privkey) will be used. SHA224, SHA256, SHA384, and SHA512 are the additional valid algorithm values. (Contributed by Jake Buchholz) v0.9 (09/07/2015) ----------------- - New option TKTAuthHeader allowing custom header(s) to be used instead of a just a Cookie. v0.8 (06/28/2012) ----------------- - new option TKTAuthPassthruBasicAuth and corresponding field in ticket ("bauth") makes it possible to specify the Basic authorization username/password in the ticket (e.g. when reverse proxying to a third party system that cannot use mod_auth_pubtkt). The credentials can optionally be encrypted in the ticket (AES-128-CBC). v0.7 (06/04/2012) ----------------- - TKTAuthPublicKey can now be set per directory/location (it is still possible to set a global default key, so existing configurations do not need to be changed) (contributed by Ivo De Decker). - TKTAuthLoginURL is now optional; if not provided, users without a valid ticket will simply get an HTTP forbidden error (contributed by Ivo De Decker). - Added Perl ticket generation module (contributed by Assaf Gordon). - Module now compiles with Apache 2.4. - Added TKTAuthBadIPURL option (contributed by John Wittkoski). - Increased max. UID length to 64 (from 32); can be changed by modifying MAX_UID_SIZE. v0.6a (02/23/2010) ------------------ - Fixed XSS vulnerability in example php-login/login.php. (reported by Thomas Hug). v0.6 (09/12/2009) ----------------- - Fixed inheritance of TKTAuthCookieName and TKTAuthBackArgName configuration directives (reported by Iaroslav Vassiliev). - Improved compatibility with HTTP 1.0 (redirect) (contributed by Frederic Planchon ). v0.5 (01/22/2009) ----------------- - Fixed parsing of cookies with escaped spaces ('+') (reported by Iaroslav Vassiliev). - Fixed errors in login.php example. v0.4 (01/18/2009) ----------------- - Replaced TKTAuthGracePeriod directive by graceperiod key in ticket (contributed by Frederic Planchon ). - Updated example PHP login page to support ticket refreshing/grace periods (contributed by Frederic Planchon ). v0.3 (01/13/2009) ----------------- - Added TKTAuthFakeBasicAuth option (when enabled, adds an Authorization header to prevent problems with username logging for requests that are handled by PHP), contributed by Frederic Planchon . - Added support for ticket refreshing (TKTAuthRefreshURL and TKTAuthGracePeriod configuration directives), contributed by Frederic Planchon . v0.2 (02/03/2008) ----------------- - Initial public release. mod_auth_pubtkt-0.13/LICENSE000077500000000000000000000073141336114244100156420ustar00rootroot00000000000000/* ==================================================================== * Portions Copyright (c) 2008-2009 Manuel Kasper . * All rights reserved. * Portions Copyright (c) 2009 Frederic Planchon . * All rights reserved. * Portions Copyright (c) 2001-2006 Open Fusion Pty Ltd (Australia). * All rights reserved. * Portions Copyright (c) 2000 Liquid Digital Information Systems, Inc. * All rights reserved. * * Portions of this software were written by Manuel Kasper and are hereby * contributed to the Apache Software Foundation for distribution under * the Apache license, as follows. * * Portions of this software were written for Open Fusion Pty. Ltd. * by Gavin Carr and are hereby contributed to the Apache Software * Foundation for distribution under the Apache license, as follows. * * Portions of this software were written for Liquid Digital Information * Systems, Inc. by Raimondas Kiveris and are hereby contributed to the * Apache Software Foundation for distribution under the Apache license, * as follows. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * 4. The names "Apache Server" and "Apache Group" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please contact * apache@apache.org. * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * 6. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Group and was originally based * on public domain software written at the National Center for * Supercomputing Applications, University of Illinois, Urbana-Champaign. * For more information on the Apache Group and the Apache HTTP server * project, please see . * */ mod_auth_pubtkt-0.13/Makefile000077500000000000000000000002461336114244100162720ustar00rootroot00000000000000TARGETS = all install clean $(TARGETS): Makedefs cd src && $(MAKE) $@ Makedefs: ./configure realclean: cd src && make clean test -f Makedefs && rm -f Makedefs mod_auth_pubtkt-0.13/README.md000066400000000000000000000455671336114244100161250ustar00rootroot00000000000000# mod_auth_pubtkt A pragmatic Web Single Sign-On (SSO) solution

Documentation

Deployment considerations

Since the "valid until" field in a ticket is necessarily in absolute time (UNIX timestamp), the clocks of the ticket-generating login server and the ticket-verifying web servers need to be more or less in sync. The longer the ticket lifetime, the less important this becomes. It's generally good practice to keep your servers' time synchronized (using NTP, for example).

Downloading and installing the module (Unix)

Download the source code for the latest version of mod_auth_pubtkt here.

Decompress the downloaded archive and run the included "configure" script, specifying the path to apxs if necessary (use where apxs to find it). The Apache version should be detected automatically (but note that the configure/make scripts haven't been tested under anything but FreeBSD and Mac OS X):

# tar xzfv mod_auth_pubtkt-0.x.tar.gz
# cd mod_auth_pubtkt-0.x
# ./configure
# make
# make install

Downloading and installing the module (Windows)

The source tarball, which you can download in the Unix section above, also contains pre-compiled modules for Apache 2.0 and 2.2 (in the "bin" subdirectory):

Decompress the downloaded archive and copy the relevant module for the version of Apache you are using into the "modules" directory inside your Apache program directory, then follow the instructions below (which apply both to Unix and Windows machines). Make sure that you use an Apache version that is bundled with OpenSSL (even if you don't use HTTPS), as mod_auth_pubtkt needs it.

Note: Windows binaries for OpenSSL (you'll need the command-line openssl.exe to generate a key pair) can be found at http://www.slproweb.com/products/Win32OpenSSL.html.

Generating a key pair

See the section below for a discussion on whether to use DSA or RSA.

DSA:

# openssl dsaparam -out dsaparam.pem 2048
# openssl gendsa -out privkey.pem dsaparam.pem
# openssl dsa -in privkey.pem -out pubkey.pem -pubout

The dsaparam.pem file is not needed anymore after key generation and can safely be deleted.

RSA:

# openssl genrsa -out privkey.pem 2048
# openssl rsa -in privkey.pem -out pubkey.pem -pubout

Module configuration

First of all, make sure that the module is loaded:

LoadModule auth_pubtkt_module libexec/apache/mod_auth_pubtkt.so
AddModule mod_auth_pubtkt.c		# Apache 1.3 only

Ensure that mod_authz_user is loaded/enabled as well.

Here's a simple VirtualHost configuration with mod_auth_pubtkt as a starting point; the configuration directives are explained below.

Note that the AuthType mod_auth_pubtkt statement is required!

<VirtualHost *:80>
    ServerName myserver.mydomain.com
    DocumentRoot /path/to/my/htdocs
    
    TKTAuthPublicKey /etc/apache2/tkt_pubkey.pem
    
    <Directory /path/to/my/htdocs>
        Order Allow,Deny
        Allow from all
        
        AuthType mod_auth_pubtkt
        TKTAuthLoginURL https://sso.mydomain.com/login
        TKTAuthTimeoutURL https://sso.mydomain.com/login?timeout=1
        TKTAuthUnauthURL https://sso.mydomain.com/login?unauth=1
        TKTAuthToken "myserver"
        require valid-user
    </Directory>
</VirtualHost>

Directives for use in server config, virtual hosts and directory/location/.htaccess scope

  • TKTAuthPublicKey
    • Path to either a DSA or RSA public key file in PEM format
    • This public key will be used to verify ticket signatures
  • TKTAuthDigest
    • String indicating what digest algorithm to use when verifying ticket signatures
    • Valid values are SHA1, DSS1, SHA224, SHA256, SHA384, and SHA512
    • If not specified, the old defaults of SHA1 (for an RSA public key) or DSS1 (for a DSA public key) will be used.

Directives for use in directory/location/.htaccess scope

  • TKTAuthLoginURL
    • URL that users without a valid ticket will be redirected to
    • The originally requested URL will be appended as a GET parameter (normally named "back", but can be changed with TKTAuthBackArgName)
  • TKTAuthTimeoutURL
    • URL that users whose ticket has expired will be redirected to
    • If not set, TKTAuthLoginURL is used
  • TKTAuthPostTimeoutURL
    • Same as TKTAuthTimeoutURL, but in case the request was a POST
    • If not set, TKTAuthTimeoutURL is used (and if that is not set either, TKTAuthLoginURL)
  • TKTAuthUnauthURL
    • URL that users whose ticket doesn't contain any of the required tokens (as set with TKTAuthToken) will be redirected to
    • If not set, TKTAuthLoginURL is used
  • TKTAuthBadIPURL (since v0.7)
    • URL that users whose IP doesn't match the cip value in the ticket (if supplied) will be redirected to
    • If not set, TKTAuthLoginURL is used
  • TKTAuthRefreshURL (since v0.3)
    • URL that users whose ticket is within the grace period (as set with the graceperiod key in the ticket) before the actual expiry will be redirected to. Only GET requests are redirected; POST requests are accepted normally. The script at this URL should check the ticket and issue a new one
    • If not set, TKTAuthLoginURL is used
  • TKTAuthHeader (since v0.9)
    • A space separated list of headers to use for finding the ticket (case insensitive). If this header specified is Cookie then the format of the value expects to be a valid cookie (subject to the TKTAuthCookieName directive). Any other header assumes the value is a simple URL-encoded value of the ticket. The first header that has content is tried and any other tickets in other header(s) are ignored. example, use Cookie first, fallback to X-My-Auth: TKTAuthHeader Cookie X-My-Auth
    • Default: Cookie
  • TKTAuthCookieName
    • Name of the authentication cookie to use
    • Default: auth_pubtkt
  • TKTAuthBackArgName
    • Name of the GET argument with the originally requested URL (when redirecting to the login page)
    • Default: back
  • TKTAuthRequireSSL
    • only accept tickets in HTTPS requests
    • Default: off
  • TKTAuthToken
    • token that must be present in a ticket for access to be granted
    • Multiple tokens may be specified; only one of them needs to be present in the ticket (i.e. any token can match, not all tokens need to match)
  • TKTAuthFakeBasicAuth (since v0.3)
    • if on, a fake Authorization header will be added to each request (username from ticket, fixed string "password" as the password). This can be used in reverse proxy situations, and to prevent PHP from stripping username information from the request (which would then not be available for logging purposes)
    • Default: off
  • TKTAuthPassthruBasicAuth (since v0.8)
    • if on, the value from the ticket's "bauth" field will be added to the request as a Basic Authorization header. This can be used in reverse proxy situations where one needs complete control over the username and password (see also TKTAuthFakeBasicAuth, which should not be used at the same time).
    • Default: off
  • TKTAuthPassthruBasicKey (since v0.8)
    • if set, the bauth value will be decrypted using the given key before it is added to the Authorization header.
    • see Ticket format for details on the encryption
    • length must be exactly 16 characters
  • TKTAuthRequireMultifactor (since v0.12)
    • If on, this directive will require the ticket's "multifactor" field to be set to 1.
    • Allows a specific directive to require additional authentication that may not be required globally.
    • Default: off
  • TKTAuthMultifactorURL (since v0.12)
    • URL that users whose ticket doesn't contain the required multifactor value will be redirected to
    • If not set, TKTAuthLoginURL is used
  • TKTAuthDebug
    • debug level (1-3, higher for more debug output)
    • default: 0
    • Note: setting TKTAuthDebug to > 0 will cause full ticket values to appear in your server's error log, which could be used to log in to other servers.

Ticket format

Authentication tickets to be processed by mod_auth_pubtkt are composed of key/value pairs, with keys and values separated by '=' and individual key/value pairs separated by semicolons (';'). The following keys are defined; mod_auth_pubtkt silently ignores unknown keys:

  • uid (required; 32 chars max.)
    • the user ID (username) that the ticket has been issued for
    • passed to the environment in REMOTE_USER
  • validuntil (required)
    • a UNIX timestamp (the number of seconds since 00:00:00 UTC on January 1, 1970) that describes when this ticket will expire
  • cip (optional; 39 chars max.)
    • the IP address of the client that this ticket was issued for
    • if present, mod_auth_pubtkt will only accept the ticket for requests that came from this IP address
    • this is usually fine for use on Intranets and is in fact recommended, but may have to be omitted in the presence of NAT or load-balancing proxy servers
  • tokens (optional; 255 chars max.)
    • a comma-separated list of words (group names etc.)
    • the presence of a given token can be made mandatory in the per-directory configuration (using the TKTAuthToken directive), effectively giving a simple form of authorization
    • the contents of this field are available to the environment in REMOTE_USER_TOKENS
  • udata (optional; 255 chars max.)
    • user data, for use by scripts; made available to the environment in REMOTE_USER_DATA
    • not interpreted by mod_auth_pubtkt
  • graceperiod (optional; since v0.4)
    • a UNIX timestamp (should be before the ticket's expiration date) after which GET requests will be redirected to the refresh URL (or the login URL, if no refresh URL is set)
  • bauth (optional; since v0.8)
    • Base64 encoded value for Authorization header (when TKTAuthPassthruBasicAuth is enabled).
    • Can optionally be encrypted (TKTAuthPassthruBasicKey option)
      • encryption is AES-128-CBC, zero padded (not PKCS7), IV in first 16 bytes
      • the plaintext username:password string should be encrypted, and the (binary) result after encryption Base64 encoded before being added to the ticket
      • for an encryption example using Mcrypt, see the included php-login/pubtkt.inc
  • multifactor (optional; since v0.12)
    • An int value (0/1) that denotes the current status of multifactor for a user
    • Defaults to 0 if this key is not present
  • sig (required)
    • a Base64 encoded RSA or DSA signature over the digest of the content of the ticket up to (but not including) the semicolon before 'sig'
    • The default digest is SHA-1, unless TKTAuthDigest has specified a different algorithm.
    • RSA: raw result; DSA: DER encoded sequence of two integers – see Dss-Sig-Value in RFC 2459
    • must be the last item in the ticket string

Here's an example of how a real (DSA) ticket looks:

uid=mkasper;cip=192.168.200.163;validuntil=1201383542;tokens=foo,bar;udata=mydata;multifactor=1;
sig=MC0CFDkCxODPml+cEvAuO+o5w7jcvv/UAhUAg/Z2vSIjpRhIDhvu7UXQLuQwSCF=

The ticket string is saved URL-encoded in a domain cookie, usually named auth_pubtkt, but this can be changed (using the TKTAuthCookieName directive).

If you would like to use a custom header instead of a cookie (or want to use both), see the TKTAuthHeader directive.

Generating tickets

An example implementation of a login/ticket generating script in PHP is provided with the distribution (in the php-login subdirectory). It uses a simple flat-file user database by default, but can easily be extended to support LDAP (e.g. using adLDAP), RADIUS and other authentication methods.

The ticket-generating (and verifying) functions are in pubtkt.inc. They use the OpenSSL command-line binary directly, for two reasons:

  • no dependency on PHP's OpenSSL extension
  • DSA signatures can be generated as well (normally, using the PHP OpenSSL extension, only RSA is supported)

For Perl users, a module and example CGI script are provided in the perl-login subdirectory of the distribution.

If you use Ruby, there's a gem created by Matt Haynes that helps with generating tickets.

For Python users, Andrey Plotnikov has created a module for generating tickets.

Whether to choose RSA or DSA

For digital signatures, two public-key schemes are commonly used: RSA and DSA. This module supports both, but you need to choose one over the other. Put simply, and assuming that both offer the same security at similar key sizes, it's mostly a decision between speed and signature (ticket/cookie) length.

  • RSA
    • signature length: as long as the modulus
      • 1024-bit modulus: 128 bytes (~172 bytes after Base64 encoding)
    • signing speed (1024-bit): about 235 signatures/s on a 2.8 GHz P4
    • verification speed (1024-bit): about 4400 verifications/s on a 2.8 GHz P4
  • DSA
    • signature length: constant (independent of key size)
      • always 2 x 160-bit, plus 6 byte DER encoding overhead = 46 bytes (sometimes 47 because of an extra leading zero byte with OpenSSL) – ~64 bytes after Base64 encoding
    • signing speed (1024-bit): about 477 signatures/s on a 2.8 GHz P4
    • verification speed (1024-bit): about 390 verifications/s on a 2.8 GHz P4

From a performance point of view, RSA is the clear winner, as each ticket only needs to be signed once, but usually verified many times on different servers. However, note that mod_auth_pubtkt caches tickets, so the verification only needs to be done once per server process and ticket (and not once per request).

If ticket size matters to you more than speed, then DSA is the better choice; otherwise, you're probably better off using RSA. In the end, it's mostly down to "religious" issues or what you're already using in your company.

Generating a ticket signature on the command line

# echo -n "uid=foobar;validuntil=123456789;tokens=;udata=" \
  | openssl dgst -dss1 -sign privkey.pem \
  | openssl enc -base64 -A

If TKTAuthDigest isn't being used, specify -dss1 for DSA, and -sha1 for RSA. Otherwise specify the TKTAuthDigest directive's algorithm (i.e. -sha256 for SHA256).

Verifying a ticket signature on the command line

Strip the signature off the ticket and Base64-decode it into a temporary file:

# echo "MC0CFQC6c....=" | openssl enc -d -base64 -A > sig.bin

Pipe the ticket value through openssl to verify the signature using the public key in pubkey.pem:

# echo "uid=foobar;validuntil=123456789;tokens=;udata=" \
  | openssl dgst -dss1 -verify pubkey.pem -signature sig.bin

Security considerations for domain cookies

Note that if rogue servers under your domain are a concern, the domain cookies used by mod_auth_pubtkt may pose a problem, since a rogue server can steal a legitimate user's ticket. This can be mitigated by marking the ticket cookie as "secure", so that it is only transported via HTTPS, which means that only servers with a valid SSL certificate for your domain can see the user's ticket (unless the user overrides security warnings in the browser). Also, including the client IP address in the ticket (as is recommended whenever possible) makes it harder to use a stolen ticket.

Another way to solve this would be to change the login server to check the "back" URL and, instead of issuing cookies directly, include the ticket in the redirect back to the web server with the desired resource, which can then install the ticket as a cookie under its own server name. This would require adding support for parsing tickets in GET parameters to mod_auth_pubtkt (could be backported from mod_auth_tkt). Also, the login server would need to keep a copy of the ticket stored in a cookie under its own server name so that the user only has to log in once, of course. Finally, since there would now be a cookie for each server, it would be much more difficult to properly log out (without closing the browser).

mod_auth_pubtkt-0.13/configure000077500000000000000000000063431336114244100165420ustar00rootroot00000000000000#!/bin/sh # # Simple configure script for mod_auth_pubtkt # # Defaults APXS=/usr/sbin/apxs test -x $APXS || unset APXS if [ -z $APXS ]; then APXS=/usr/bin/apxs test -x $APXS || unset APXS fi if [ -z $APXS ]; then APXS=/usr/bin/apxs2 test -x $APXS || unset APXS fi ME=`basename $0` DIR=`dirname $0` if [ $DIR = '.' ]; then DIR=`pwd` fi usage() { echo "usage: $ME [--apxs=/path/to/apxs] [--apachever=<1.3|2|2.2|2.4>] [--debug]" } die() { echo $* exit 2 } # Retrograde option handling to allow for primitive getopts ac_prev= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval "$ac_prev=\$ac_option" ac_prev= continue fi ac_optarg=`expr "x$ac_option" : 'x[^=]*=\(.*\)'` case $ac_option in --apxs=*) APXS=$ac_optarg ;; --apxs) ac_prev=APXS ;; --apachever=*) VERSION=$ac_optarg ;; --debug) DEBUG="-g -Wall -ansi -Wno-implicit-function-declaration -Wno-long-long" ;; -h | --help) usage; exit 0 ;; *) usage; exit 1 ;; esac done # Sanity checks test "$ac_prev" = "APXS" && die "Error: option '--apxs' requires an argument" test -n "$APXS" || die "Error: cannot locate apxs (use --apxs=/path/to/apxs)" test -x $APXS || die "Error: missing apxs '$APXS' (use --apxs=/path/to/apxs)" # Get Apache version if [ -z "$VERSION" ]; then HTTPD=`$APXS -q SBINDIR`/`$APXS -q TARGET` test -x $HTTPD || die "Error: cannot determine apache version (use --apachever=<1.3|2|2.2|2.4>)" VERSION=`$HTTPD -v | head -1 | sed -e 's/.*Apache\///' -e 's/^\([0-9]\.[0-9]*\).*/\1/'` fi # Standardise test $VERSION = '1' && VERSION=1.3 test $VERSION = '2.0' && VERSION=2 test $VERSION = '20' && VERSION=2 test $VERSION = '22' && VERSION=2.2 test $VERSION = '24' && VERSION=2.4 if [ $VERSION != '1.3' -a $VERSION != '2' -a $VERSION != '2.2' -a $VERSION != '2.4' ]; then die "Error: apache version '$VERSION' not supported" fi # Generate Makedefs DIV="#-------------------------------------------------------------------------" WARNING="# Generated by $ME, do not edit!" test -f Makedefs && rm -f Makedefs test -f Makedefs && die "Error deleting Makedefs" echo $DIV >> Makedefs echo $WARNING >> Makedefs echo >> Makedefs echo "VERSION = $VERSION" >> Makedefs echo "APXS = $APXS" >> Makedefs test -n "$DEBUG" && echo "CFLAGS += $DEBUG" >> Makedefs if [ "$VERSION" = "1.3" ]; then echo "CFLAGS += -DAPACHE13" >> Makedefs echo "TARGET = mod_auth_pubtkt.so" >> Makedefs else if [ $VERSION = "2.2" ]; then echo "CFLAGS += -DAPACHE22" >> Makedefs elif [ $VERSION = "2.4" ]; then echo "CFLAGS += -DAPACHE24" >> Makedefs fi echo "TARGET = mod_auth_pubtkt.la" >> Makedefs fi echo "BASEDIR = $DIR" >> Makedefs # proper handling of Universal Binaries under Mac OS X HTTPD="`${APXS} -q SBINDIR`/`${APXS} -q TARGET`" if test -x /usr/bin/lipo; then ARCHITECTURES=`/usr/bin/lipo -info $HTTPD | sed -e 's/.*://'` for ARCH in $ARCHITECTURES; do echo "CFLAGS += -arch ${ARCH}" >> Makedefs echo "LDFLAGS += -arch ${ARCH}" >> Makedefs done fi if [ -d /usr/share/man ]; then echo "MANPATH = /usr/share/man" >> Makedefs else echo "MANPATH = /usr/man" >> Makedefs fi echo >> Makedefs echo $WARNING >> Makedefs echo $DIV >> Makedefs # Finish with a 'make clean' make -s clean mod_auth_pubtkt-0.13/mod_auth_pubtkt.conf000066400000000000000000000025271336114244100206730ustar00rootroot00000000000000LoadModule auth_pubtkt_module modules/mod_auth_pubtkt.so #TKTAuthPublicKey conf.d/auth/pubkey-rsa.pem # # # Order allow,deny # Allow from all # # AuthType mod_auth_pubtkt # TKTAuthLoginURL http://sso.company.com/sso/login # TKTAuthTimeoutURL http://sso.company.com/sso/login?timeout=1 # TKTAuthUnauthURL http://sso.company.com/sso/login?unauth=1 # # # This defaults to "Cookie" if not specified. You may specify any number # # of headers to try and they will be attempted in order. # # # TKTAuthHeader Cookie X-Then-Your-Custom # # TKTAuthCookieName "auth_pubtkt" # TKTAuthRequireSSL off # # require valid-user # # Reverse proxy configuration with pass-through basic authentication # (ticket must contain field 'bauth' with username:password in Base64; # may optionally be encrypted with AES-128-CBC before Base64-encoding) # # ProxyPass http://my.basicauth-site.com # ProxyPassReverse http://my.basicauth-site.com # # AuthType mod_auth_pubtkt # TKTAuthLoginURL http://sso.company.com/sso/login # TKTAuthTimeoutURL http://sso.company.com/sso/login?timeout=1 # TKTAuthUnauthURL http://sso.company.com/sso/login?unauth=1 # require valid-user # # TKTAuthPassthruBasicAuth on # TKTAuthPassthruBasicKey "must_be_16_chars" # mod_auth_pubtkt-0.13/mod_auth_pubtkt.spec000066400000000000000000000035431336114244100206770ustar00rootroot00000000000000Summary: Ticket-based authorization module for the Apache HTTP Server Name: mod_auth_pubtkt Version: 0.12 Release: 0 License: Apache Group: Applications/System Source0: https://neon1.net/mod_auth_pubtkt/mod_auth_pubtkt-0.12.tar.gz Source1: mod_auth_pubtkt.conf URL: https://neon1.net/mod_auth_pubtkt/ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot BuildRequires: httpd-devel, openssl-devel Requires: httpd-mmn = %(cat %{_includedir}/httpd/.mmn || echo httpd-devel missing) Requires: openssl %description Single sign-on module for Apache, based on mod_auth_tkt. %prep %setup -q %build ./configure make %install rm -rf %{buildroot} mkdir -p %{buildroot}/%{_libdir}/httpd/modules mkdir -p %{buildroot}/%{_sysconfdir}/httpd/conf.d install -m 755 src/.libs/mod_auth_pubtkt.so %{buildroot}/%{_libdir}/httpd/modules install -m 644 %{_sourcedir}/auth_pubtkt.conf %{buildroot}/%{_sysconfdir}/httpd/conf.d/ rm -f %{buildroot}/%{_libdir}/httpd/modules/{*.la,*.so.*} %clean rm -rf %{buildroot} %files %defattr(-,root,root,-) %{_libdir}/httpd/modules/*.so %config %{_sysconfdir}/httpd/conf.d/auth_pubtkt.conf %changelog * Tue Mar 13 2018 Nick Ramser 0.12-0 - Updated to latest version of mod_auth_pubtkt [0.12] * Fri Dec 16 2016 Jake Buchholz 0.10-0 - Updated to latest version of mod_auth_pubtkt [0.10] * Wed Sep 09 2015 Manuel Kasper 0.9-0 - Updated to latest version of mod_auth_pubtkt [0.9] * Tue Mar 26 2013 John Wittkoski 0.8-0 - Updated to latest version of mod_auth_pubtkt [0.8] * Mon Jun 04 2012 Manuel Kasper 0.7-0 - Updated to latest version of mod_auth_pubtkt [0.7] * Sat Sep 19 2009 Omachonu Ogali 0.6-0 - Updated to latest version of mod_auth_pubtkt [0.6] * Tue Sep 08 2009 Omachonu Ogali 0.5-0 - Initial package creation mod_auth_pubtkt-0.13/perl-login/000077500000000000000000000000001336114244100166755ustar00rootroot00000000000000mod_auth_pubtkt-0.13/perl-login/README.perl.md000066400000000000000000000041361336114244100211210ustar00rootroot00000000000000Perl wrapper for mod_auth_pubtkt ================================ Prerequisites ------------- 1. Perl's IPC::Run3" module. Running: $ sudo cpan IPC::Run3 should "just work". 2. Generate a pair of private+public keys, as explained here: https://neon1.net/mod_auth_pubtkt/install.html Running the following commands should work: # Genereate a private RSA key $ openssl genrsa -out key.priv.pem 1024 # Geenrate a public RSA key $ openssl rsa -in key.priv.pem -out key.pub.pem -pubout Module Usage Example -------------------- See the 'test_pubtkt.pl' script for a complete generate ticket + verify ticket example. run `perldoc mod_auth_pubtkt.pm` for more details. Login Page Example ------------------ See ./perl-login/minimal-cgi/login.pl for a bare-bones CGI login script. use "gordon" and password "12345" to test the login mechanism. A reasonable Apache configuration for the login server would be: ``` ServerName sso.mydomain.com DocumentRoot /path/to/mod_auth_pubtkt/perl-login/minimal_cgi Order Allow,Deny Allow from all Options +ExecCGI DirectoryIndex login.pl AddHandler cgi-script .pl ``` And a corresponding Handler's apache configuration would be: ``` ServerName myserver.mydomain.com DocumentRoot /path/to/my/htdocs TKTAuthPublicKey /path/to/mod_auth/pubtkt/perl-login/key.pub.pem Order Allow,Deny Allow from all AuthType mod_auth_pubtkt TKTAuthLoginURL https://sso.mydomain.com/login.pl TKTAuthTimeoutURL https://sso.mydomain.com/login.pl?timeout=1 TKTAuthUnauthURL https://sso.mydomain.com/login.pl?unauth=1 require valid-user ``` To try out this minimal CGI, you'll need to update the configuration paramters (e.g. domain name) in `login.pl`. mod_auth_pubtkt-0.13/perl-login/key.priv.pem000066400000000000000000000015671336114244100211600ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDH07jDDv84n83YW28/FO+OEKDFN9tyUJEwwG9aiAWiJ91yncf1 A+FL3qMJVrXEG3J8mtuwnWDQxGIXU6nbZZlr6RC5Q5DX5SqatLen1T4Vumw4/Oxu zt/LAJCfD2gcudcMbkpq+5KwM3PqOslhSBE4sbtsMvWarMjIwfM4qf8YpwIDAQAB AoGAWP/WaaITUB/0qWlH6fukGk0TneMb7RUvJVx/+/1bLPa+bZ8SgPECdi0pxi4F dNuYqSC7ujTN+w2MdsE/hMUCiEDPOusRUcjBJr5xLJByvxsqR7Qdf1uBmRhS2xlC Pd9LsdoUxJy7Djl/A3Su+pizVaCcP4pxlHi2QBKbnG+R4mkCQQDopBua8ZkOOvyr b+R9aSpGt+8CluXAWQJaB14Q707YpHE35qepPCZOeAWz1SA5gZjxSnuFpT+dMvJ2 hM2ZYg4LAkEA2+QmgERpVOf90BkNPLy1X9F8pCL4P4k5uDWAWcBjJcuEdIXSPFp+ l+PKvqj5n+M6yXwNCxPU7GRbMbXe2bmtVQJBALUxvF599dvjjZBpYelb05WpBPtb VC7wJKjCPD2sZhjOW3BSshtZwew0Bxz9zk975QdqH7MD9fwWBkrRPOFOQekCQBgp NHXJjo1OxFu2NPckgQVbPkfGs+I/UMFF16mE8x/3AcHP5m7NPrWvyNo0NOF1lUMI R2KdNjsXN9H5etgPh9UCQQCB4P/pM4MfzXMDrWRv5IxIOZ6cwi6/xzpqc1UyKk0y tExTGm1HiQJhdVR/ppn79wMgz53jIZ6my0On6fMaCzR0 -----END RSA PRIVATE KEY----- mod_auth_pubtkt-0.13/perl-login/key.pub.pem000066400000000000000000000004201336114244100207510ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDH07jDDv84n83YW28/FO+OEKDF N9tyUJEwwG9aiAWiJ91yncf1A+FL3qMJVrXEG3J8mtuwnWDQxGIXU6nbZZlr6RC5 Q5DX5SqatLen1T4Vumw4/Oxuzt/LAJCfD2gcudcMbkpq+5KwM3PqOslhSBE4sbts MvWarMjIwfM4qf8YpwIDAQAB -----END PUBLIC KEY----- mod_auth_pubtkt-0.13/perl-login/minimal_cgi/000077500000000000000000000000001336114244100211455ustar00rootroot00000000000000mod_auth_pubtkt-0.13/perl-login/minimal_cgi/login.pl000077500000000000000000000124511336114244100226200ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use FindBin; use lib "$FindBin::Bin/../"; use mod_auth_pubtkt; use CGI qw(:standard :cgi-lib); use CGI::Carp qw/fatalsToBrowser/; use URI::Escape; use Data::Dump qw/dump/; $CGI::POST_MAX=1024 * 10; # max 10K posts $CGI::DISABLE_UPLOADS = 1; # no uploads sub show_login_page; sub show_post_page; sub post_successful_login; sub validate_login; ## ## Configuration parameters. ## These must match the corresponding "mod_auth_pubtkt" settings on every apache handler server. ## my $mod_auth_pubtkt_cookie = "auth_pubtkt"; # mod_auth_pubtkt's TKTAuthCookieName setting. my $mod_auth_cookie_domain = ".cshl.edu"; # the domain for which this cookie is valid. my $tokens = ""; # mod_auth_pubtkt's TKTAuthToken setting. This default implementation doesn't send any tokens. my $user_data = "" ; # This default implementation doesn't send any user-data. my $valid_until_delta = 86400 ; # Valid for one day my $grace_period = 3600 ; # Grace period of one hour my $use_client_ip = 1 ; # should the ticket/cookie contain the client's IP address? ## TODO: DO NOT USE THESE keys in a production settings. ## These are just for debugging/testing. my $key_type = "rsa"; my $digest = undef; # defaults to sha1 or dss1, depending on $key_type my $public_key = "$FindBin::Bin/../key.pub.pem"; my $private_key = "$FindBin::Bin/../key.priv.pem"; =head1 Technical note =head2 This login script can be invoked in one of several ways: =over 2 =item C request, with possibly a C, C, C CGI parameters. foo bar =item C request, with a C cookie, (and possibly C, C, C CGI parameters) foo bar =item C request, with a possible C CGI GET paramter, and C and C POST CGI parameters. foo bar =back =cut ########################################################### ## CGI Script Starts here ########################################################### if (request_method() eq "GET") { show_login_page(); } elsif (request_method() eq "POST") { ## User tried to login, verify username/password, and issue a ticket. if (validate_login()) { post_successful_login(); } else { show_login_page("Login failed. Please try again"); } } else { ## We don't susport anything else other than GET/POST. no HEAD, PUT, DELETE, etc. die "What's going on? unknown request method: " . request_method() ; } ########################################################### ## CGI Script End ########################################################### sub show_login_page { my $message = shift || ""; my $back = url_param('back') || ""; if ($back) { $back = "back=" .uri_escape($back); } print header(); # HTTP header, back to apache #The simplest login HTML
page print <

Login

$message

Name:
Password:
HTML } =pod Get the username/password from the POST parameters, try to authenticate the user. return FALSE on any failure, or TRUE if login was successful. TODO: Implement it in which every way you want (DB, LDAP, PAM, Text file, etc.) =cut sub validate_login { my $username = param("username") || ""; my $password = param("password") || ""; # This seems pretty much bullet-proof secure :) return ( $username eq "gordon" && $password eq "12345" ); } =pod Generate the cookie, with the signed ticket, and all other parameters. =cut sub generate_pubtkt_cookie { my ($user_id) = shift or croak "Error: missing user_id parameter."; my $ticket = pubtkt_generate( privatekey => $private_key, keytype => $key_type, digest => $digest, clientip => ($use_client_ip) ? remote_addr() : undef, userid => $user_id, validuntil => time() + $valid_until_delta, graceperiod=> $grace_period, tokens => $tokens, userdata => $user_data); my $cookie = cookie(-name => $mod_auth_pubtkt_cookie, -value => $ticket, -domain=> $mod_auth_cookie_domain, -path => "/"); return $cookie; } =pod What to do after the user successfully logged on? (either by entering username/password, or by renewing a grace-period) 1. Set a new mod_auth_pubtkt cookie 2. If there's a "back" CGI parameter, redirect the user there. 3. If there's no "back", show something else. =cut sub post_successful_login { my $cookie = generate_pubtkt_cookie(param("username")); my $back = url_param('back') || "" ; if ($back) { ## Send the user back were he/she came from, this time with a cookie print redirect( -url => $back, -cookie => $cookie ); exit(0); } ## ## Don't knwo where the user came from, show some generic message (and set the cookie). ## possibly show "portal" - a list of other services using this ticket authentication system. ## print header(-cookie => $cookie); # HTTP header, back to apache print <

Good, now go away

HTML } mod_auth_pubtkt-0.13/perl-login/mod_auth_pubtkt.pm000066400000000000000000000157121336114244100224320ustar00rootroot00000000000000package mod_auth_pubtkt; =pod =head1 NAME pubtkt - Generate Tickets for mod_auth_pubtkt =head1 VERSION version 0.1 =cut our $VERSION = '0.1'; =pod =head1 SYNOPSIS use mod_auth_pubtkt; ## NOTE: "key.priv.pem" and "key.pub.pem" must already exist. ## running these should suffice: ## openssl genrsa -out key.priv.pem 1024 ## openssl rsa -in key.priv.pem -out key.pub.pem -pubout my $ticket = pubtkt_generate( privatekey => "key.priv.pem", keytype => "rsa", digest => undef, # or sha1, dss1, sha224, sha256, sha384, or sha512 clientip => undef, # or a valid IP address userid => "102", # or any ID that makes sense to your application, e.g. email validuntil => time() + 86400, # valid for one day graceperiod=> 3600, # grace period of an hour tokens => undef, # comma separated string of tokens. userdata => undef # any application specific data to pass. ); ## $ticket string will look something like: ## "uid=102;validuntil=1337899939;graceperiod=1337896339;tokens=;udata=;sig=h5qR" \ ## "yZZDl8PfW8wNxPYkcOMlAxtWuEyU5bNAwEFT9lztN3I7V13SaGOHl+U6wB+aMkvvLQiaAfD2xF/Hl" \ ## "+QmLDEvpywp98+5nRS+GeihXTvEMRaA4YVyxb4NnZujCZgX8IBhP6XBlw3s7180jxE9I8DoDV8bDV" \ ## "k/2em7yMEzLns=" my $ok = pubtkt_verify ( publickey => "key.pub.pem", keytype => "rsa", digest => undef, ticket => $ticket ); die "Ticket verification failed.\n" if not $ok; =head1 DESCRIPTION This module generates and verify a mod_auth_pubtkt-compatible ticket string, which should be used as a cookie with the rest of the B ( L ) system. =head3 Common scenario: =over 2 =item 1. On the login server side, write perl code to authenticate users (using Apache's authenetication, LDAP, DB, etc.). =item 2. Once the user is authenticated, call C to generate a ticket, and send it back to the user as a cookie. =item 3. Redirect the user back to the server he/she came from. =back =head1 PREREQUISITES B must be installed (and available on the $PATH). L is required to run the openssl executables. =head1 BUGS Probably many. =head1 LICENSE Copyright (C) 2012 A. Gordon ( gordon at cshl dot edu ). Apache License, same as the rest of B =head1 AUTHORS A. Gordon, heavily based on the PHP code from B. =head1 SEE ALSO L C for a usage example. =cut require Exporter; our @ISA=qw(Exporter); our @EXPORT = qw/pubtkt_generate pubtkt_verify pubtkt_parse/; use strict; use warnings; use Carp; use MIME::Base64; use File::Temp qw/tempfile/; use IPC::Run3; ## On unix, assume it's on the $PATH. ## On Windows - you're on your own. ## TODO: make this user-configurable. my $openssl_bin = "openssl"; =pod =cut sub pubtkt_generate { my %args = @_; my $private_key_file = $args{privatekey} or croak "Missing \"privatekey\" parameter"; croak "Invalid \"privatekey\" value ($private_key_file): file doesn't exist/not readable" unless -r $private_key_file; my $keytype = $args{keytype} or croak "Missing \"keytype\" parameter"; croak "Invalid \"keytype\" value ($keytype): expecting 'dsa' or 'rsa'\n" unless $keytype eq "dsa" || $keytype eq "rsa"; my $user_id = $args{userid} or croak "Missing \"userid\" parameter"; my $valid_until = $args{validuntil} or croak "Missing \"validuntil\" parameter"; croak "Invalid \"validuntil\" value ($valid_until), expecting a numeric value." unless $valid_until =~ /^\d+$/; my $grace_period = $args{graceperiod} || ""; croak "Invalid \"graceperiod\" value ($grace_period), expecting a numeric value." unless $grace_period eq "" || $grace_period =~ /^\d+$/; my $client_ip = $args{clientip} || ""; ##TODO: better IP address validation croak "Invalid \"client_ip\" value ($client_ip), expecting a valid IP address." unless $client_ip eq "" || $client_ip =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; my $tokens = $args{tokens} || ""; my $user_data = $args{userdata} || ""; # Generate Ticket String my $tkt = "uid=$user_id;" ; $tkt .= "cip=$client_ip;" if $client_ip; $tkt .= "validuntil=$valid_until;"; $tkt .= "graceperiod=" . ($valid_until - $grace_period) . ";" if $grace_period; $tkt .= "tokens=$tokens;"; $tkt .= "udata=$user_data"; my $algorithm_param = '-'.$args{digest} or ( $keytype eq "dsa" ) ? "-dss1" : "-sha1"; croak "Invalid \"digest\" value ($args{digest}), expecting sha1, dss1, sha224, sha256, sha384, or sha512." if index(" sha1 dss1 sha224 sha256 sha384 sha512 ", " $args{digest} ") == -1; my @cmd = ( $openssl_bin, "dgst", $algorithm_param, "-binary", "-sign", $private_key_file ) ; my ($stdin, $stdout, $stderr); $stdin = $tkt; run3 \@cmd, \$stdin, \$stdout, \$stderr; my $exitcode = $?; if ($exitcode != 0) { warn "pubtkt_generate failed: openssl returned exit code $exitcode, stderr = $stderr\n"; return; } $tkt .= ";sig=" . encode_base64($stdout,""); #2nd param = no EOL. return $tkt; } sub pubtkt_verify { my %args = @_; my $public_key_file = $args{publickey} or croak "Missing \"publickey\" parameter"; croak "Invalid \"publickey\" value ($public_key_file): file doesn't exist/not readable" unless -r $public_key_file; my $keytype = $args{keytype} or croak "Missing \"keytype\" parameter"; croak "Invalid \"keytype\" value ($keytype): expecting 'dsa' or 'rsa'\n" unless $keytype eq "dsa" || $keytype eq "rsa"; my $algorithm_param = '-'.$args{digest} or ( $keytype eq "dsa" ) ? "-dss1" : "-sha1"; croak "Invalid \"digest\" value ($args{digest}), expecting sha1, dss1, sha224, sha256, sha384, or sha512." if index(" sha1 dss1 sha224 sha256 sha384 sha512 ", " $args{digest} ") == -1; my $ticket_str = $args{ticket} or croak "Missing \"ticket\" parameter"; # Extract base64'd signature text my ($ticket_data, $sig_base64) = split /;sig=/, $ticket_str; warn "Pubtkt.pm: missing \"sig=\" in ticket ($ticket_str)" unless $sig_base64; return unless $sig_base64; # Decode base64 signature, and store in a temporary file my $sig_bin = decode_base64($sig_base64); warn "Pubtkt.pm: invalid base64 signature from ticket ($ticket_str)" unless length($sig_bin)>0; my ($fh, $temp_sig_file) = tempfile("pubtkt.XXXXXXXXX", UNLINK=>1); print $fh $sig_bin or die "Failed to write signature data: $!"; close $fh or die "Failed to write signature data: $!"; # verify signature using openssl my @cmd = ( $openssl_bin, "dgst", $algorithm_param, "-verify", $public_key_file, "-signature", $temp_sig_file); my ($stdin, $stdout, $stderr); $stdin = $ticket_data; run3 \@cmd, \$stdin, \$stdout, \$stderr; my $exitcode = $?; return unless $exitcode == 0; return 1 if ( $stdout eq "Verified OK\n" ) ; return ; } sub pubtkt_parse { my $tkt = shift or croak "missing ticket string parameter"; my @fields = split /;/, $tkt; my %values = map { split (/=/, $_, 2) } @fields; return %values; } 1; mod_auth_pubtkt-0.13/perl-login/test_pubtkt.pl000077500000000000000000000022541336114244100216100ustar00rootroot00000000000000#!/usr/bin/env perl =pod Perl implementation of mod_auth_pubtkt ticket generateion. see https://neon1.net/mod_auth_pubtkt/ for more details. Copyright (C) 2012 A. Gordon ( gordon at cshl dot edu ) LICENSE: Apacle License (see LICENSE file) See README.perl.md file for more details. =cut use strict; use warnings; use mod_auth_pubtkt; ## ## Generate a ticket ## my $ticket = pubtkt_generate( privatekey => "key.priv.pem", keytype => "rsa", digest => undef, clientip => undef, userid => "102", validuntil => time() + 86400, graceperiod=> 3600, tokens => undef, userdata => undef); print $ticket,"\n"; ## ## Verify the same ticket ## my $ok = pubtkt_verify ( publickey => "key.pub.pem", keytype => "rsa", digest => undef, ticket => $ticket ); die "Ticket verification failed.\n" if not $ok; ## ## Change something in the ticket, then verify again (which should fail) ## $ticket =~ s/uid=102/uid=103/; $ok = pubtkt_verify ( publickey => "key.pub.pem", keytype => "rsa", digest => undef, ticket => $ticket ); die "Error: forged ticket verified successfully, something is terribly wrong." if $ok; print "all ok\n"; mod_auth_pubtkt-0.13/php-login/000077500000000000000000000000001336114244100165225ustar00rootroot00000000000000mod_auth_pubtkt-0.13/php-login/login.php000077500000000000000000000226001336114244100203460ustar00rootroot00000000000000 */ require_once("pubtkt.inc"); /* Set the parameters relevant to your domain below. WARNING: do not use the example keys provided with the distribution in production - otherwise, anyone could fake your tickets! Generate your own key! */ $domain = ".example.com"; $secure_cookie = false; /* set to true if all your web servers use HTTPS */ $logfile = "private/login.log"; $privkeyfile = "private/tkt_privkey_dsa.pem"; $pubkeyfile = "private/tkt_pubkey_dsa.pem"; $keytype = "DSA"; $digest = "default"; $localuserdb = "private/users.txt"; $default_timeout = 86400; $default_graceperiod = 3600; /* authenticates the user with the given password against the local user database; returns an array with the following information: success => true/false, tokens => array(tokens that should be given to user), timeout => how long the ticket should be valid (in seconds) graceperiod => how long the ticket should be refreshed before expiring (in seconds) */ function local_login($username, $password) { $user_info = get_login_info($username); if (isset($user_info) && is_array($user_info)) { $out_info = $user_info['data']; $out_info['success'] = ($user_info['password'] === $password || $user_info['password'] === md5($password)); return $out_info; } return array('success' => false); } function get_login_info($username) { global $localuserdb, $default_timeout, $default_graceperiod; $fd = @fopen($localuserdb, "r"); if ($fd) { while (!feof($fd)) { $line = trim(fgets($fd)); if (preg_match("/^\s*#/", $line)) continue; if (!$line) continue; list($cusername,$cpassword,$tokens,$timeout,$graceperiod) = explode("\t", $line); if (!$timeout) $timeout = $default_timeout; if (!$graceperiod) $graceperiod = $default_graceperiod; if ($cusername === $username) { fclose($fd); return array('login' => $cusername, 'password' => $cpassword, 'data' => array('tokens' => explode(",", $tokens), 'timeout' => $timeout, 'graceperiod' => $graceperiod)); } } fclose($fd); } return NULL; } /* very simple file-based login auditing */ function log_login($ip, $username, $success) { global $logfile; $fd = @fopen($logfile, "a"); if ($fd) { fputs($fd, time() . "\t$ip\t$username\t" . ($success ? "1" : "0") . "\n"); fclose($fd); } } /* use the last username, if known (saves the user from having to type that all the time) */ $username = $_COOKIE['sso_lastuser']; $password = ""; $err = ""; $loginsuccess = false; if ($_GET['back']) { /* Extract the host name of the 'back' URL so we can tell the user when there will be no point in trying to log in, as the cookie won't be available to the target server (e.g. if users try to access a server by its IP address instead of by its proper host name), thus avoiding confusion. */ $urlp = parse_url($_GET['back']); $reshost = $urlp['host']; $server_allowed = preg_match("/$domain\$/", $reshost); } else { $server_allowed = true; } if ($_POST) { $username = strtolower($_POST['username']); /* always lower-case usernames for easier matching */ $password = $_POST['password']; /* try to authenticate */ $res = local_login($username, $password); if ($res['success']) { log_login($_SERVER['REMOTE_ADDR'], $username, true); $tkt_validuntil = time() + $res['timeout']; /* generate the ticket now and set a domain cookie */ $tkt = pubtkt_generate($privkeyfile, $keytype, $digest, $username, $_SERVER['REMOTE_ADDR'], $tkt_validuntil, $res['graceperiod'], join(",", $res['tokens']), ""); setcookie("auth_pubtkt", $tkt, 0, "/", $domain, $secure_cookie); setcookie("sso_lastuser", $username, time()+30*24*60*60); if ($_GET['back']) { header("Location: " . $_GET['back']); exit; } } else { log_login($_SERVER['REMOTE_ADDR'], $username, false); $loginerr = "Authentication failed. Please try again."; } } else { if ($_COOKIE['auth_pubtkt']) { /* Extract data from existing cookie so we can nicely offer the user a logout function. No attempt at verifying the ticket is made, as that's not necessary at this point. */ $ticket = pubtkt_parse($_COOKIE['auth_pubtkt']); $tkt_validuntil = $ticket['validuntil']; $tkt_graceperiod = $ticket['graceperiod']; $tkt_uid = $ticket['uid']; /* Checking validity of the ticket and if we are between begin of grace period and end of ticket validity. If so we can refresh ticket */ if (pubtkt_verify($pubkeyfile, $keytype, $digest, $ticket) && isset($tkt_graceperiod) && is_numeric($tkt_graceperiod) && ($tkt_graceperiod <= time()) && (time() <= $tkt_validuntil)) { /* getting user information */ $user_info = get_login_info($tkt_uid); if (isset($user_info) && is_array($user_info)) { $tkt_validuntil = time() + $user_info['data']['timeout']; /* generate the ticket now and set a domain cookie */ $tkt = pubtkt_generate($privkeyfile, $keytype, $digest, $tkt_uid, $ticket['cip'], $tkt_validuntil, $user_info['data']['graceperiod'], join(",", $user_info['data']['tokens']), ""); setcookie("auth_pubtkt", $tkt, 0, "/", $domain, $secure_cookie); setcookie("sso_lastuser", $tkt_uid, time()+30*24*60*60); if ($_GET['back']) { header("Location: " . $_GET['back']); exit; } } else { /* User is not present in user database (anymore) - delete the cookie */ setcookie("auth_pubtkt", false, time() - 86400, "/", $domain, $secure_cookie); } } } } ?> mod_auth_pubtkt Single Sign-On

Single Sign-On

The server is unknown.

You have successfully signed on.

Your login ticket will expire on .

Your session has ended due to a timeout; please log in again.

You don't have permission to access the desired resource on ;
you may try logging in again with different credentials.

= time() && $ticket['cip'] == $_SERVER['REMOTE_ADDR']): ?>

You are currently logged on as ''.

Username:
Password:
mod_auth_pubtkt-0.13/php-login/logo.gif000077500000000000000000000017361336114244100201630ustar00rootroot00000000000000GIF89a»³óóóWWWçççooo“““ÛÛÛccc···ŸŸŸ‡‡‡ÃÃÃÏÏÏ{{{«««KKKÿÿÿ!ù,»ÿðÉI«½8ëÍ»ÿ`(Ždižhª®lë¾p,Ïtmßx®ïsQð“…H,FF¢ˆÁP:JIÃ1šFALÜÂqœ<¯VNvÎŒ¯çÚ¶+ùFËš4ü’VÖgkŠ[9ÇÈ©qM PL  ŒˆŠ†LCw ‡Ž lA…„O ?_’ ‰ ¡ ˆC£¯V L¢ ¿Š} ¿ÁÃS  »Ï ÞŸdØšåØíÔ^PÌTS÷‚OÒÑ”: G¦‚5¨@ ‹w¹›­ºr¡{ €mEž¾µPÀ/@0 F8›Á/fÉàFÂ@¼ÃWÄðËÅ_ DÂc<}‡ìÝF$aÉø@²È,·,ÂÊ.Ç,sˆÍlóÍ8ç¬óÎD;mod_auth_pubtkt-0.13/php-login/logout.php000077500000000000000000000014231336114244100205470ustar00rootroot00000000000000 */ $domain = ".example.com"; if ($_POST['logout']) { /* only do this if there really has been a POST; otherwise we could be fooled by pre-caching browsers etc. */ setcookie("auth_pubtkt", "", time() - 86400, "/", $domain, true); } else { header("Location: login.php"); exit; } ?> mod_auth_pubtkt Single Sign-On

Single Sign-On

You are now logged out.

mod_auth_pubtkt-0.13/php-login/private/000077500000000000000000000000001336114244100201745ustar00rootroot00000000000000mod_auth_pubtkt-0.13/php-login/private/.htaccess000077500000000000000000000000371336114244100217750ustar00rootroot00000000000000Order allow,deny Deny from all mod_auth_pubtkt-0.13/php-login/private/tkt_privkey_dsa.pem000077500000000000000000000012341336114244100241040ustar00rootroot00000000000000-----BEGIN DSA PRIVATE KEY----- MIIBuwIBAAKBgQDQGQreMtpc9zH1xd9q9qRU4OqZBdDSDMNO5n0HkfPe5B5Jl8a1 bV2zh2EXO683/SQFTFUlZgIuemegPzSlH8A0tcr8RY7tye7uhZQA333s1BRm/QVL EBePjhO3KK4EUC7T9d2plPK/dHNhAoZ9sTRv66cUoGB2/oB3gt6ng56WSQIVAKw2 b7nSCbPJ+A7cpUq2nEUU5CzNAoGAbCDXytaGXmUrvFZkuV42hCNJkyCEMmdfLjbK Y7LCyBAB/5RbNfssV5WRknD8ZCFyjbII+qStJUN/a0pD4WDpXtuttuMqk9rq0NPR ZDRaIO1aGzxyqk0bx6cd49uc7JtW+2Xsie9mvsTXCXKztH/DJi63gT0IFZmh38sR T2A1BGMCgYADP0tOdprWD83Msf3y8j38kGZkWIXDCPSlks1gFrqfO8h1VECCrTnk bb2qsA91rw/rdksDSionJdLYiagSIYs9mjAQGaTHEHz7bek9CVaAw5CdtB7xdGBI uhqGTwI1uZXRzixmVf36k01bzqjJygnv9xoze9kenzum8gs2KrVSLgIVAJieH1W9 M+7hyYfSMq6n6pTEvHuG -----END DSA PRIVATE KEY----- mod_auth_pubtkt-0.13/php-login/private/tkt_privkey_rsa.pem000077500000000000000000000015731336114244100241300ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQClgoPHTuJJd+zbKqS9ULxn1XGBdTVUkQUs02fTkgXOP2M5HhAR uYE7S1D0OXet66G21zCyi29xCHzJvvSkwFkUc/rmTqjJQX7qqG8N8GdE+CwhoBJB WxThhRINUzcgmI/0+bADFCzZH+1bSXpgI0Te82Xh9q2ydt1CaEZNrXhsNQIDAQAB AoGBAInJviuPYJQJYwaS2dNCA5ft+jDsgxmEIerPlQBt8Kdj3hcPEBGxfgT/DsZD kX8ZS+gL/l6l9oEDr9/FOaZu+7jUJk8YBaSGJIdaadHMVo6Zcq370G2lAqvwuqIR GRhtEPBVoJOGaVAdp38flkjndJ/a6RlXAjnln3ADX+7Ja2gBAkEA2KwPVfe1x33R rGjFSs+yh97nBerTdPrN796NH7W/NwFtU8cb7sZMPPXWkH0EhSfdQO27W/vKZoES jkqFDv/B0QJBAMONIqc3Beh4QC1+CyIGQPvumgcEjQdbnGzuWFt3k4BK+eD8+shT E2Khf+J61zLrkvdYOYWx+jHfw5UkPPBUGSUCQQDEjfGrjvc8bYsT2EeBwkC2uSLi X1BMQmknPMDRD1LTV1wSMAHK7eCjPHDUylSbZrpz+DWLEDNgIZ7vrfJe1OzBAkAq IC9E4l+NcsowgqSXUc2R+BaKSHqxCRUrijTSj3HhA0XNJ/JGUU8twiiwe1H8kC76 xbCukmaZc+DIMiRGiXdZAkEAuGQe+QkWIrMkSsJiRazS9u7+jtjEwQ/AN4yQh5dl mlXLNueY0sboibVuDeMdpyJUxoMvJBG0SLGVXG1a3y8Qtg== -----END RSA PRIVATE KEY----- mod_auth_pubtkt-0.13/php-login/private/tkt_pubkey_dsa.pem000077500000000000000000000012161336114244100237120ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIIBtjCCASsGByqGSM44BAEwggEeAoGBANAZCt4y2lz3MfXF32r2pFTg6pkF0NIM w07mfQeR897kHkmXxrVtXbOHYRc7rzf9JAVMVSVmAi56Z6A/NKUfwDS1yvxFju3J 7u6FlADffezUFGb9BUsQF4+OE7corgRQLtP13amU8r90c2EChn2xNG/rpxSgYHb+ gHeC3qeDnpZJAhUArDZvudIJs8n4DtylSracRRTkLM0CgYBsINfK1oZeZSu8VmS5 XjaEI0mTIIQyZ18uNspjssLIEAH/lFs1+yxXlZGScPxkIXKNsgj6pK0lQ39rSkPh YOle26224yqT2urQ09FkNFog7VobPHKqTRvHpx3j25zsm1b7ZeyJ72a+xNcJcrO0 f8MmLreBPQgVmaHfyxFPYDUEYwOBhAACgYADP0tOdprWD83Msf3y8j38kGZkWIXD CPSlks1gFrqfO8h1VECCrTnkbb2qsA91rw/rdksDSionJdLYiagSIYs9mjAQGaTH EHz7bek9CVaAw5CdtB7xdGBIuhqGTwI1uZXRzixmVf36k01bzqjJygnv9xoze9ke nzum8gs2KrVSLg== -----END PUBLIC KEY----- mod_auth_pubtkt-0.13/php-login/private/tkt_pubkey_rsa.pem000077500000000000000000000004201336114244100237240ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClgoPHTuJJd+zbKqS9ULxn1XGB dTVUkQUs02fTkgXOP2M5HhARuYE7S1D0OXet66G21zCyi29xCHzJvvSkwFkUc/rm TqjJQX7qqG8N8GdE+CwhoBJBWxThhRINUzcgmI/0+bADFCzZH+1bSXpgI0Te82Xh 9q2ydt1CaEZNrXhsNQIDAQAB -----END PUBLIC KEY----- mod_auth_pubtkt-0.13/php-login/private/users.txt000077500000000000000000000001371336114244100221020ustar00rootroot00000000000000# Format: # username password (tokens) (timeout) (graceperiod) testuser testpass token1,token2 mod_auth_pubtkt-0.13/php-login/pubtkt.inc000077500000000000000000000174771336114244100205510ustar00rootroot00000000000000 */ /* Set this to the path to your OpenSSL binary. This is usually something like /usr/bin/openssl on Unix-like systems. On Windows, you must manually get openssl.exe *and* the necessary libraries (usually libeay32.dll and ssleay32.dll) and put them together in a directory where they're accessible to PHP. */ define("OPENSSL_PATH", "/usr/bin/openssl"); /* Generate a ticket. Parameters: privkeyfile path to private key file (PEM format) privkeytype type of private key ("RSA" or "DSA") digest digest algorithm ("default" is sha1 or dss1) also valid: sha224, sha256, sha384, and sha512 uid user ID/username clientip client IP address (optional; can be empty or null) validuntil expiration timestamp (e.g. time() + 86400) tokens comma-separated list of tokens (optional) udata user data (optional) bauth basic auth username:password (for passthru, optional; can optionally use pubtkt_encrypt_bauth() to encrypt it) multifactor boolean (optional). Notes if multifactor authentication is present Returns: ticket string, or FALSE on failure */ function pubtkt_generate($privkeyfile, $privkeytype, $digest, $uid, $clientip, $validuntil, $graceperiod, $tokens, $udata, $bauth = null, $multifactor = false) { /* format ticket string */ $tkt = "uid=$uid;"; if ($clientip) $tkt .= "cip=$clientip;"; $tkt .= "validuntil=$validuntil;"; if ( isset($graceperiod) && is_numeric($graceperiod) && $graceperiod > 0 ) { $tkt .= "graceperiod=".($validuntil-$graceperiod).";"; } if (!empty($bauth)) $tkt .= "bauth=" . base64_encode($bauth) . ";"; if ($multifactor) { $multifactor_int = 1; } else { $multifactor_int = 0; } $tkt .= "tokens=$tokens;udata=$udata;multifactor=$multifactor_int"; if ($privkeytype == "DSA") $algoparam = "-dss1"; else $algoparam = "-sha1"; if ($digest != "default") $algoparam = "-" . $digest; $fd = @proc_open(OPENSSL_PATH . " dgst $algoparam -binary -sign " . escapeshellarg($privkeyfile), array(0 => array("pipe", "r"), 1 => array("pipe", "w")), $pipes); if (!is_resource($fd)) { echo "Cannot start openssl"; return false; } fwrite($pipes[0], $tkt); fclose($pipes[0]); $sig = fread($pipes[1], 8192); fclose($pipes[1]); $res = proc_close($fd); if ($res != 0) { echo "openssl returned exit status $res"; return false; } return $tkt . ";sig=" . base64_encode($sig); } /* Generate a ticket using php-only functions (only for RSA signature type) Parameters: privkey private key as string (PEM format) uid user ID/username clientip client IP address (optional; can be empty or null) validuntil expiration timestamp (e.g. time() + 86400) tokens comma-separated list of tokens (optional) udata user data (optional) bauth basic auth username:password (for passthru, optional; can optionally use pubtkt_encrypt_bauth() to encrypt it) Returns: ticket string, or FALSE on failure */ function pubtkt_generate_php($privkey, $uid, $clientip, $validuntil, $graceperiod, $tokens, $udata, $bauth = null) { /* format ticket string */ $tkt = "uid=$uid;"; if ($clientip) $tkt .= "cip=$clientip;"; $tkt .= "validuntil=$validuntil;"; if ( isset($graceperiod) && is_numeric($graceperiod) && $graceperiod > 0 ) { $tkt .= "graceperiod=".($validuntil-$graceperiod).";"; } if (!empty($bauth)) $tkt .= "bauth=" . base64_encode($bauth) . ";"; $tkt .= "tokens=$tokens;udata=$udata"; $pkeyid = openssl_get_privatekey($privkey); if($pkeyid === false) return false; $res = openssl_sign($tkt, $sig, $pkeyid); openssl_free_key($pkeyid); if(!$res) return false; return $tkt . ";sig=" . base64_encode($sig); } /* Validate a ticket. Parameters: pubkeyfile path to public key file (PEM format) pubkeytype type of public key ("RSA" or "DSA") digest digest algorithm ("default" is sha1 or dss1) also valid: sha224, sha256, sha384, and sha512 ticket ticket string (including signature) Returns: ticket valid true/false */ function pubtkt_verify($pubkeyfile, $pubkeytype, $digest, $ticket) { /* strip off signature */ $sigpos = strpos($ticket, ";sig="); if ($sigpos === false) return false; /* no signature found */ $ticketdata = substr($ticket, 0, $sigpos); $sigdata = base64_decode(substr($ticket, $sigpos + 5)); if (!$sigdata) return false; /* write binary signature to temporary file */ $tmpfn = tempnam("/tmp", "tktsig"); $tmpfd = fopen($tmpfn, "wb"); fwrite($tmpfd, $sigdata); fclose($tmpfd); if ($pubkeytype == "DSA") $algoparam = "-dss1"; else $algoparam = "-sha1"; if ($digest != "default") $algoparam = "-" . $digest; /* check DSA signature */ $fd = proc_open(OPENSSL_PATH . " dgst $algoparam -verify " . escapeshellarg($pubkeyfile) . " -signature " . escapeshellarg($tmpfn), array(0 => array("pipe", "r"), 1 => array("pipe", "w")), $pipes); fwrite($pipes[0], $ticketdata); fclose($pipes[0]); $res = trim(fgets($pipes[1])); fclose($pipes[1]); proc_close($fd); unlink($tmpfn); return ($res === "Verified OK"); } /* Validate a ticket using PHP only (only for RSA signature type) Parameters: pubkey public key as string (PEM format) ticket ticket string (including signature) Returns: ticket valid true/false */ function pubtkt_verify_php($pubkey, $ticket) { /* strip off signature */ $sigpos = strpos($ticket, ";sig="); if ($sigpos === false) return false; /* no signature found */ $ticketdata = substr($ticket, 0, $sigpos); $sigdata = base64_decode(substr($ticket, $sigpos + 5)); if (!$sigdata) return false; $ret = openssl_verify($ticketdata, $sigdata, $pubkey); if($ret === 1) return true; return false; } /* Parse a ticket into its key/value pairs and return them as an associative array for easier use. */ function pubtkt_parse($ticket) { $tkt = array(); $kvpairs = explode(";", $ticket); foreach ($kvpairs as $kvpair) { list($key,$val) = explode("=", $kvpair, 2); $tkt[$key] = $val; } return $tkt; } /* Encrypt a "bauth" passthru basic authentication value (username:password) with the given key (must be exactly 16 characters and match the key configured on the server). The result is in binary, but can be passed to pubtkt_generate() directly, as it will be Base64-encoded. Requires Mcrypt! */ function pubtkt_encrypt_bauth($bauth, $key) { if (strlen($key) != 16) return null; $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_DEV_URANDOM); mcrypt_generic_init($td, $key, $iv); $encrypted = mcrypt_generic($td, $bauth); mcrypt_generic_deinit($td); mcrypt_module_close($td); return $iv . $encrypted; } /* Decrypt a "bauth" passthru basic authentication value * and return only the password with the given key (must be exactly 16 * characters). The input $bauth string should be binary, * so it has to be decoded using base64_decode beforehand. * Requires mcrypt! */ function pubtkt_decrypt_bauth($bauth, $key) { if (strlen($key) != 16) return null; $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); $iv_s = mcrypt_enc_get_iv_size($td); $iv = substr($bauth, 0, $iv_s); $c_t = substr($bauth, $iv_s); mcrypt_generic_init($td, $key, $iv); $decrypted = mdecrypt_generic($td, $c_t); mcrypt_generic_deinit($td); mcrypt_module_close($td); list($user, $pass) = explode(':', trim($decrypted)); return $pass; } ?> mod_auth_pubtkt-0.13/php-login/style.css000077500000000000000000000007251336114244100204030ustar00rootroot00000000000000* { font-family: Arial, Helvetica, sans-serif; font-size: 12pt; } h2 { font-size: 16pt; } .logintbl { border-collapse: collapse; } .logintbl td, .logintbl th { padding: 0.3em; border: 1px solid #bbb; background-color: #f7f7f7; } .logintbl th { text-align: left; background-color: #e0e0e0; padding-right: 1em; } .logintbl tr.blank td { background-color: white; border: none; } .errmsg { color: red; } a { text-decoration: none; font-size: 0.8em; } mod_auth_pubtkt-0.13/src/000077500000000000000000000000001336114244100154145ustar00rootroot00000000000000mod_auth_pubtkt-0.13/src/Makefile000077500000000000000000000004561336114244100170640ustar00rootroot00000000000000 include ../Makedefs MOD = mod_auth_pubtkt all: $(TARGET) $(TARGET): mod_auth_pubtkt.c ap_compat.h $(APXS) -c -Wc,"-Wall -ansi $(CFLAGS)" -Wl,"$(LDFLAGS)" -l crypto $(MOD).c install: $(TARGET) $(APXS) -i $(TARGET) clean: -rm -f $(MOD).o $(MOD).so $(MOD).la $(MOD).lo $(MOD).slo -rm -rf .libs mod_auth_pubtkt-0.13/src/ap_compat.h000077500000000000000000000221641336114244100175400ustar00rootroot00000000000000/* Compatibility mappings from apache 2.0 api calls back to apache 1.3.x */ /* Derived from apr_compat.h and apu_compat.h */ #ifndef AP_COMPAT_H #define AP_COMPAT_H #define APR_INLINE ap_inline #define AP_MODULE_DECLARE_DATA MODULE_VAR_EXPORT #define APR_OFFSETOF XtOffsetOf #define APR_EGENERAL 0 #define APR_INADDR_NONE INADDR_NONE /* Omit Apache2 status from ap_log_rerror, adding APLOG_NOERRNO instead */ #define ap_log_error(mark, level, status, ...) ap_log_error(mark, level | APLOG_NOERRNO, __VA_ARGS__) #define ap_log_rerror(mark, level, status, ...) ap_log_rerror(mark, level | APLOG_NOERRNO, __VA_ARGS__) #define apr_uri_default_port_for_scheme ap_default_port_for_scheme #define apr_pool_t pool #define apr_md5_ctx_t ap_md5_ctx_t #define apr_md5_encode ap_MD5Encode #define apr_md5_final ap_MD5Final #define apr_md5_init ap_MD5Init #define apr_md5_update ap_MD5Update #define apr_array_append ap_append_arrays #define apr_array_cat ap_array_cat #define apr_array_header_t array_header #define apr_array_pstrcat ap_array_pstrcat #define apr_pool_free_blocks_num_bytes ap_bytes_in_free_blocks #define apr_pool_num_bytes ap_bytes_in_pool #define apr_check_file_time ap_check_file_time #define apr_filetype_e ap_filetype_e #define apr_pool_cleanup_for_exec ap_cleanup_for_exec #define apr_pool_clear ap_clear_pool #define apr_table_clear ap_clear_table #define apr_array_copy ap_copy_array #define apr_array_copy_hdr ap_copy_array_hdr #define apr_table_copy ap_copy_table #define apr_cpystrn ap_cpystrn #define apr_day_snames ap_day_snames #define apr_pool_destroy ap_destroy_pool #define apr_time_exp_t ap_exploded_time_t #define apr_fnmatch ap_fnmatch #define apr_getopt ap_getopt #define apr_inet_addr ap_inet_addr #define apr_pool_alloc_init ap_init_alloc #define apr_is_empty_table ap_is_empty_table #define apr_fnmatch_test ap_is_fnmatch #define apr_pool_cleanup_kill ap_kill_cleanup #define apr_array_make ap_make_array #define apr_pool_sub_make ap_make_sub_pool #define apr_table_make ap_make_table #define apr_month_snames ap_month_snames #define apr_pool_note_subprocess ap_note_subprocess #define apr_pool_cleanup_null ap_null_cleanup #define apr_filepath_merge ap_os_canonical_filename /* #define apr_filepath_merge ap_os_case_canonical_filename */ #define apr_dso_load ap_os_dso_load #define apr_dso_unload ap_os_dso_unload #define apr_dso_sym ap_os_dso_sym #define apr_dso_error ap_os_dso_error /** @deprecated @see apr_filepath_merge * @warning apr_filepath_merge rejects invalid filenames */ /* #define ap_os_is_filename_valid apr_filepath_merge */ #define apr_proc_kill ap_os_kill /* #define ap_os_systemcase_canonical_filename apr_filepath_merge */ #define apr_table_overlap ap_overlap_tables #define apr_table_overlay ap_overlay_tables #define apr_palloc ap_palloc #define apr_pcalloc ap_pcalloc #define apr_pool_join ap_pool_join #define apr_psprintf ap_psprintf #define apr_pstrcat ap_pstrcat #define apr_pstrdup ap_pstrdup #define apr_pstrndup ap_pstrndup #define apr_array_push ap_push_array #define apr_pvsprintf ap_pvsprintf #define apr_pool_cleanup_register ap_register_cleanup #define apr_proc_other_child_register ap_register_other_child #define apr_pool_cleanup_run ap_run_cleanup #define apr_signal ap_signal #define apr_snprintf ap_snprintf #define apr_table_add ap_table_add #define apr_table_addn ap_table_addn #define apr_table_do ap_table_do #define apr_table_elts ap_table_elts #define apr_table_get ap_table_get #define apr_table_merge ap_table_merge #define apr_table_mergen ap_table_mergen #define apr_table_set ap_table_set #define apr_table_setn ap_table_setn #define apr_table_unset ap_table_unset #define apr_proc_other_child_unregister ap_unregister_other_child #define apr_password_validate ap_validate_password #define apr_vformatter ap_vformatter #define apr_vsnprintf ap_vsnprintf #define apr_wait_t ap_wait_t #define apr_isalnum ap_isalnum #define apr_isalpha ap_isalpha #define apr_iscntrl ap_iscntrl #define apr_isdigit ap_isdigit #define apr_isgraph ap_isgraph #define apr_islower ap_islower #define apr_isascii ap_isascii #define apr_isprint ap_isprint #define apr_ispunct ap_ispunct #define apr_isspace ap_isspace #define apr_isupper ap_isupper #define apr_isxdigit ap_isxdigit #define apr_tolower ap_tolower #define apr_toupper ap_toupper #define APR_USEC_PER_SEC AP_USEC_PER_SEC #define APR_RFC822_DATE_LEN AP_RFC822_DATE_LEN #define APR_OVERLAP_TABLES_MERGE AP_OVERLAP_TABLES_MERGE #define APR_OVERLAP_TABLES_SET AP_OVERLAP_TABLES_SET #define apr_base64_decode ap_base64decode #define apr_base64_decode_binary ap_base64decode_binary #define apr_base64_decode_len ap_base64decode_len #define apr_base64_encode ap_base64encode #define apr_base64_encode_binary ap_base64encode_binary #define apr_base64_encode_len ap_base64encode_len #define apr_hook_deregister_all ap_hook_deregister_all #define apr_hook_sort_register ap_hook_sort_register #define apr_hook_debug_show ap_show_hook /* -------------------------------------------------------------------- * the following symbols were moved from httpd-2.0/.../util_date.[ch] */ #define apr_date_parse_http ap_parseHTTPdate #define apr_date_checkmask ap_checkmask /* -------------------------------------------------------------------- * the following symbols were moved from httpd-2.0/.../util_xml.[ch] */ #define ap_text apr_text #define ap_text_header apr_text_header #define ap_text_append apr_text_append #define AP_XML_NS_DAV_ID APR_XML_NS_DAV_ID #define AP_XML_NS_NONE APR_XML_NS_NONE #define AP_XML_NS_ERROR_BASE APR_XML_NS_ERROR_BASE #define AP_XML_NS_IS_ERROR(e) APR_XML_NS_IS_ERROR(e) #define AP_XML_ELEM_IS_EMPTY(e) APR_XML_ELEM_IS_EMPTY(e) #define ap_xml_attr apr_xml_attr #define ap_xml_elem apr_xml_elem #define ap_xml_doc apr_xml_doc #define ap_xml_to_text apr_xml_to_text #define AP_XML_X2T_FULL APR_XML_X2T_FULL #define AP_XML_X2T_INNER APR_XML_X2T_INNER #define AP_XML_X2T_LANG_INNER APR_XML_X2T_LANG_INNER #define AP_XML_X2T_FULL_NS_LANG APR_XML_X2T_FULL_NS_LANG #define ap_xml_empty_elem apr_xml_empty_elem #define ap_xml_quote_string apr_xml_quote_string #define ap_xml_quote_elem apr_xml_quote_elem #define ap_xml_insert_uri apr_xml_insert_uri #define AP_XML_GET_URI_ITEM(a,i) APR_XML_GET_URI_ITEM(a,i) /* From Apache2 httpd.h */ # define ap_strchr(s, c) strchr(s, c) /* From Apache2 http_config.h */ # define AP_INIT_NO_ARGS(directive, func, mconfig, where, help) \ { directive, func, mconfig, where, RAW_ARGS, help } # define AP_INIT_RAW_ARGS(directive, func, mconfig, where, help) \ { directive, func, mconfig, where, RAW_ARGS, help } # define AP_INIT_TAKE1(directive, func, mconfig, where, help) \ { directive, func, mconfig, where, TAKE1, help } # define AP_INIT_ITERATE(directive, func, mconfig, where, help) \ { directive, func, mconfig, where, ITERATE, help } # define AP_INIT_TAKE2(directive, func, mconfig, where, help) \ { directive, func, mconfig, where, TAKE2, help } # define AP_INIT_TAKE12(directive, func, mconfig, where, help) \ { directive, func, mconfig, where, TAKE12, help } # define AP_INIT_ITERATE2(directive, func, mconfig, where, help) \ { directive, func, mconfig, where, ITERATE2, help } # define AP_INIT_TAKE13(directive, func, mconfig, where, help) \ { directive, func, mconfig, where, TAKE13, help } # define AP_INIT_TAKE23(directive, func, mconfig, where, help) \ { directive, func, mconfig, where, TAKE23, help } # define AP_INIT_TAKE123(directive, func, mconfig, where, help) \ { directive, func, mconfig, where, TAKE123, help } # define AP_INIT_TAKE3(directive, func, mconfig, where, help) \ { directive, func, mconfig, where, TAKE3, help } # define AP_INIT_FLAG(directive, func, mconfig, where, help) \ { directive, func, mconfig, where, FLAG, help } /* use strtok_r instead of apr_strtok */ #define apr_strtok strtok_r /* Apache 1.3 doesn't have ap_unescape_url_keep2f */ static char x2c(const char *what) { register char digit; digit = ((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0')); digit *= 16; digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0')); return (digit); } static int ap_unescape_url_keep2f(char *url) { register int badesc, badpath; char *x, *y; badesc = 0; badpath = 0; /* Initial scan for first '%'. Don't bother writing values before * seeing a '%' */ y = strchr(url, '%'); if (y == NULL) { return OK; } for (x = y; *y; ++x, ++y) { if (*y != '%') { *x = *y; } else { if (!apr_isxdigit(*(y + 1)) || !apr_isxdigit(*(y + 2))) { badesc = 1; *x = '%'; } else { char decoded; decoded = x2c(y + 1); if (decoded == '\0') { badpath = 1; } else { *x = decoded; y += 2; } } } } *x = '\0'; if (badesc) { return HTTP_BAD_REQUEST; } else if (badpath) { return HTTP_NOT_FOUND; } else { return OK; } } #endif /* AP_COMPAT_H */ mod_auth_pubtkt-0.13/src/mod_auth_pubtkt.c000077500000000000000000001154371336114244100207670ustar00rootroot00000000000000/* mod_auth_pubtkt based on mod_auth_tkt by Open Fusion (http://www.openfusion.com.au/labs/mod_auth_tkt/) Copyright 2008-2009 Manuel Kasper . See the LICENSE file included in the distribution for the license terms. */ #include "mod_auth_pubtkt.h" /* ----------------------------------------------------------------------- */ /* Global variables */ auth_pubtkt_cache *cache = NULL; #if APR_HAS_THREADS apr_thread_mutex_t *cache_lock; #endif /* ----------------------------------------------------------------------- */ /* Initializers */ #ifdef APACHE13 void auth_pubtkt_init(server_rec *s, pool *p) { ap_add_version_component("mod_auth_pubtkt/" PUBTKT_AUTH_VERSION); ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS, s, "mod_auth_pubtkt: version %s", PUBTKT_AUTH_VERSION); } void auth_pubtkt_child_init(server_rec *s, pool *p) { /* CRYPTO_malloc_init(); */ ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); cache_init(p, s); } #else static int auth_pubtkt_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { ap_add_version_component(p, "mod_auth_pubtkt/" PUBTKT_AUTH_VERSION); ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS, s, "mod_auth_pubtkt: version %s", PUBTKT_AUTH_VERSION); return DECLINED; } static void auth_pubtkt_child_init(apr_pool_t *p, server_rec *s) { /* CRYPTO_malloc_init(); */ ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); cache_init(p, s); } #endif /* Create per-dir config structures */ static void* create_auth_pubtkt_config(apr_pool_t *p, char* path) { auth_pubtkt_dir_conf *conf = apr_palloc(p, sizeof(*conf)); conf->directory = path; conf->login_url = NULL; conf->timeout_url = NULL; conf->post_timeout_url = NULL; conf->unauth_url = NULL; conf->auth_token = apr_array_make(p, 0, sizeof (char *)); conf->auth_header_name = NULL; conf->auth_cookie_name = NULL; conf->back_arg_name = NULL; conf->refresh_url = NULL; conf->badip_url = NULL; conf->require_ssl = -1; conf->debug = -1; conf->fake_basic_auth = -1; conf->passthru_basic_auth = -1; conf->pubkey = NULL; conf->digest = NULL; conf->passthru_basic_key = NULL; conf->require_multifactor = -1; conf->multifactor_url = NULL; return conf; } /* Merge per-dir config structures */ static void* merge_auth_pubtkt_config(apr_pool_t *p, void* parent_dirv, void* subdirv) { auth_pubtkt_dir_conf *parent = (auth_pubtkt_dir_conf *) parent_dirv; auth_pubtkt_dir_conf *subdir = (auth_pubtkt_dir_conf *) subdirv; auth_pubtkt_dir_conf *conf = apr_palloc(p, sizeof(*conf)); conf->directory = (subdir->directory) ? subdir->directory : parent->directory; conf->login_url = (subdir->login_url) ? subdir->login_url : parent->login_url; conf->timeout_url = (subdir->timeout_url) ? subdir->timeout_url : parent->timeout_url; conf->post_timeout_url = (subdir->post_timeout_url) ? subdir->post_timeout_url : parent->post_timeout_url; conf->unauth_url = (subdir->unauth_url) ? subdir->unauth_url : parent->unauth_url; conf->auth_token = (subdir->auth_token->nelts > 0) ? subdir->auth_token : parent->auth_token; conf->auth_header_name = (subdir->auth_header_name) ? subdir->auth_header_name : parent->auth_header_name; conf->auth_cookie_name = (subdir->auth_cookie_name) ? subdir->auth_cookie_name : parent->auth_cookie_name; conf->back_arg_name = (subdir->back_arg_name) ? subdir->back_arg_name : parent->back_arg_name; conf->refresh_url = (subdir->refresh_url) ? subdir->refresh_url : parent->refresh_url; conf->badip_url = (subdir->badip_url) ? subdir->badip_url : parent->badip_url; conf->require_ssl = (subdir->require_ssl >= 0) ? subdir->require_ssl : parent->require_ssl; conf->debug = (subdir->debug >= 0) ? subdir->debug : parent->debug; conf->fake_basic_auth = (subdir->fake_basic_auth >= 0) ? subdir->fake_basic_auth : parent->fake_basic_auth; conf->passthru_basic_auth = (subdir->passthru_basic_auth >= 0) ? subdir->passthru_basic_auth : parent->passthru_basic_auth; conf->pubkey = (subdir->pubkey) ? subdir->pubkey : parent->pubkey; conf->digest = (subdir->digest) ? subdir->digest : parent->digest; conf->passthru_basic_key = (subdir->passthru_basic_key) ? subdir->passthru_basic_key : parent->passthru_basic_key; conf->require_multifactor = (subdir->require_multifactor >= 0) ? subdir->require_multifactor : parent->require_multifactor; conf->multifactor_url = (subdir->multifactor_url) ? subdir->multifactor_url : parent->multifactor_url; return conf; } /* ----------------------------------------------------------------------- */ /* Caching */ static void cache_init(apr_pool_t *p, server_rec* s) { int i; cache = (auth_pubtkt_cache*)apr_palloc(p, sizeof(auth_pubtkt_cache)); if (cache == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, APR_SUCCESS, s, "TKT: cache init failed!"); return; } cache->nextslot = 0; for (i = 0; i < CACHE_SIZE; i++) cache->slots[i].hash = 0; #if APR_HAS_THREADS apr_thread_mutex_create(&(cache_lock), APR_THREAD_MUTEX_DEFAULT, p); #endif } static int cache_get(const char *ticket, auth_pubtkt *tkt) { int i, found = 0; unsigned int hash; if (cache == NULL) return 0; #if APR_HAS_THREADS apr_thread_mutex_lock(cache_lock); #endif hash = cache_hash(ticket); for (i = 0; i < CACHE_SIZE; i++) { if (hash == cache->slots[i].hash) { if (strcmp(ticket, cache->slots[i].ticket) == 0) { /* found it */ memcpy(tkt, &cache->slots[i].tkt, sizeof(*tkt)); found = 1; break; } } } #if APR_HAS_THREADS apr_thread_mutex_unlock(cache_lock); #endif return found; } /* Put a new ticket into the cache. */ static void cache_put(const char *ticket, auth_pubtkt *tkt) { if (cache == NULL) return; #if APR_HAS_THREADS apr_thread_mutex_lock(cache_lock); #endif cache->slots[cache->nextslot].hash = cache_hash(ticket); strncpy(cache->slots[cache->nextslot].ticket, ticket, MAX_TICKET_SIZE); cache->slots[cache->nextslot].ticket[MAX_TICKET_SIZE] = 0; memcpy(&cache->slots[cache->nextslot].tkt, tkt, sizeof(*tkt)); cache->nextslot++; if (cache->nextslot >= CACHE_SIZE) cache->nextslot = 0; #if APR_HAS_THREADS apr_thread_mutex_unlock(cache_lock); #endif } static unsigned int cache_hash(const char *ticket) { char *p; unsigned int hash = 0; for (p = (char*)ticket; *p; p++) hash = hash * 33 + *p; if (hash == 0) hash = 1; /* unlikely case */ return hash; } /* ----------------------------------------------------------------------- */ /* Command-specific functions */ module AP_MODULE_DECLARE_DATA auth_pubtkt_module; static const char *set_auth_pubtkt_token(cmd_parms *cmd, void *cfg, const char *param) { char **new; auth_pubtkt_dir_conf *conf = (auth_pubtkt_dir_conf*)cfg; new = (char**)apr_array_push(conf->auth_token); *new = apr_pstrdup(cmd->pool, param); return NULL; } static const char *setup_pubkey(cmd_parms *cmd, void *cfg, const char *param) { FILE *fkey; const char *pubkeypath; auth_pubtkt_dir_conf *conf = (auth_pubtkt_dir_conf*)cfg; /* read public key file */ pubkeypath = ap_server_root_relative(cmd->pool, (char*)param); if (!pubkeypath) return apr_pstrcat(cmd->pool, cmd->cmd->name, ": Invalid file path ", param, NULL); fkey = fopen(pubkeypath, "r"); if (fkey == NULL) return apr_psprintf(cmd->pool, "unable to open public key file '%s'", pubkeypath); conf->pubkey = PEM_read_PUBKEY(fkey, NULL, NULL, NULL); fclose(fkey); if (conf->pubkey == NULL) return apr_psprintf(cmd->pool, "unable to read public key file '%s': %s", pubkeypath, ERR_reason_error_string(ERR_get_error())); /* check key type */ if (!(EVP_PKEY_id(conf->pubkey) == EVP_PKEY_RSA || EVP_PKEY_id(conf->pubkey) == EVP_PKEY_RSA2 || EVP_PKEY_id(conf->pubkey) == EVP_PKEY_DSA || EVP_PKEY_id(conf->pubkey) == EVP_PKEY_DSA1 || EVP_PKEY_id(conf->pubkey) == EVP_PKEY_DSA2 || EVP_PKEY_id(conf->pubkey) == EVP_PKEY_DSA3 || EVP_PKEY_id(conf->pubkey) == EVP_PKEY_DSA4)) return apr_psprintf(cmd->pool, "unsupported key type %d", EVP_PKEY_id(conf->pubkey) ); /* set default digest algorigthm - old defaults for now */ if (EVP_PKEY_id(conf->pubkey) == EVP_PKEY_RSA || EVP_PKEY_id(conf->pubkey) == EVP_PKEY_RSA2) conf->digest = EVP_sha1(); else { conf->digest = EVP_sha1(); } return NULL; } static const char *setup_digest(cmd_parms *cmd, void *cfg, const char *param) { auth_pubtkt_dir_conf *conf = (auth_pubtkt_dir_conf*)cfg; if (strcasecmp(param, "SHA1") == 0) { conf->digest = EVP_sha1(); } else if (strcasecmp(param, "DSS1") == 0) { conf->digest = EVP_sha1(); } else if (strcasecmp(param, "SHA224") == 0) { conf->digest = EVP_sha224(); } else if (strcasecmp(param, "SHA256") == 0) { conf->digest = EVP_sha256(); } else if (strcasecmp(param, "SHA384") == 0) { conf->digest = EVP_sha384(); } else if (strcasecmp(param, "SHA512") == 0) { conf->digest = EVP_sha512(); } else { return apr_pstrcat(cmd->pool, cmd->cmd->name, ": Invalid digest algorithm ", param, NULL); } return NULL; } static const char *setup_passthru_basic_key(cmd_parms *cmd, void *cfg, const char *param) { auth_pubtkt_dir_conf *conf = (auth_pubtkt_dir_conf*)cfg; if (strlen(param) != PASSTHRU_AUTH_KEY_SIZE) return apr_psprintf(cmd->pool, "wrong length of passthru basic auth key"); conf->passthru_basic_key = param; return NULL; } static const char *set_auth_pubtkt_debug(cmd_parms *cmd, void *cfg, const char *param) { auth_pubtkt_dir_conf *conf = (auth_pubtkt_dir_conf*)cfg; int debug = atoi(param); if (debug < 0) return ("Debug level must be positive"); if (debug == INT_MAX) return ("Integer overflow or invalid number"); conf->debug = debug; return NULL; } /* Command table */ static const command_rec auth_pubtkt_cmds[] = { AP_INIT_TAKE1("TKTAuthLoginURL", ap_set_string_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, login_url), OR_AUTHCFG, "URL to redirect to if authentication fails"), AP_INIT_TAKE1("TKTAuthTimeoutURL", ap_set_string_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, timeout_url), OR_AUTHCFG, "URL to redirect to if cookie times-out"), AP_INIT_TAKE1("TKTAuthPostTimeoutURL", ap_set_string_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, post_timeout_url), OR_AUTHCFG, "URL to redirect to if cookie times-out doing a POST"), AP_INIT_TAKE1("TKTAuthUnauthURL", ap_set_string_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, unauth_url), OR_AUTHCFG, "URL to redirect to if valid user without required token"), AP_INIT_RAW_ARGS("TKTAuthHeader", ap_set_string_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, auth_header_name), OR_AUTHCFG, "name of the header to use for the ticket"), AP_INIT_TAKE1("TKTAuthCookieName", ap_set_string_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, auth_cookie_name), OR_AUTHCFG, "name to use for ticket cookie"), AP_INIT_TAKE1("TKTAuthBackArgName", ap_set_string_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, back_arg_name), OR_AUTHCFG, "name to use for back url argument (NULL for none)"), AP_INIT_TAKE1("TKTAuthRefreshURL", ap_set_string_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, refresh_url), OR_AUTHCFG, "URL to redirect to if cookie reach grace period"), AP_INIT_TAKE1("TKTAuthBadIPURL", ap_set_string_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, badip_url), OR_AUTHCFG, "URL to redirect to if request IP doesn't match cookie IP"), AP_INIT_FLAG("TKTAuthRequireSSL", ap_set_flag_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, require_ssl), OR_AUTHCFG, "whether to refuse non-HTTPS requests"), AP_INIT_FLAG("TKTAuthFakeBasicAuth", ap_set_flag_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, fake_basic_auth), OR_AUTHCFG, "whether to refuse non-HTTPS requests"), AP_INIT_FLAG("TKTAuthPassthruBasicAuth", ap_set_flag_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, passthru_basic_auth), OR_AUTHCFG, "whether to add a basic Authorization header based on ticket field 'bauth'"), AP_INIT_ITERATE("TKTAuthToken", set_auth_pubtkt_token, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, auth_token), OR_AUTHCFG, "token required to access this area (NULL for none)"), AP_INIT_TAKE1("TKTAuthPublicKey", setup_pubkey, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, pubkey), OR_ALL, "public key file to use for verifying signatures"), AP_INIT_TAKE1("TKTAuthDigest", setup_digest, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, digest), OR_ALL, "digest algorigthm to use for verifying signatures"), AP_INIT_TAKE1("TKTAuthPassthruBasicKey", setup_passthru_basic_key, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, pubkey), OR_ALL, "key to use for decrypting passthru field, must be exactly 16 characters"), AP_INIT_ITERATE("TKTAuthDebug", set_auth_pubtkt_debug, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, debug), OR_AUTHCFG, "debug level (1-3, higher for more debug output)"), AP_INIT_FLAG("TKTAuthRequireMultifactor", ap_set_flag_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, require_multifactor), OR_AUTHCFG, "whether to require a mulitfactor login flag in the ticket"), AP_INIT_TAKE1("TKTAuthMultifactorURL", ap_set_string_slot, (void *)APR_OFFSETOF(auth_pubtkt_dir_conf, multifactor_url), OR_AUTHCFG, "URL to redirect to if multifactor is required but not present in the ticket"), {NULL}, }; /* ----------------------------------------------------------------------- */ /* Support functions */ /* Parse ticket (assuming it has already been validated). Returns 1 on success or 0 on error. */ static int parse_ticket(request_rec *r, char *ticket, auth_pubtkt *tkt) { auth_pubtkt_dir_conf *conf = ap_get_module_config(r->per_dir_config, &auth_pubtkt_module); char *tok, *last; for (tok = apr_strtok(ticket, ";", &last); tok; tok = apr_strtok(NULL, ";", &last)) { /* split key/value pair */ char *key, *value; char *eqptr = strchr(tok, '='); if (eqptr == NULL) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r, "TKT parse_ticket: bad key/value pair: '%s'", tok); continue; } *eqptr = 0; key = tok; value = (eqptr + 1); if (strcmp(key, "uid") == 0) strncpy(tkt->uid, value, sizeof(tkt->uid)-1); else if (strcmp(key, "cip") == 0) strncpy(tkt->clientip, value, sizeof(tkt->clientip)-1); else if (strcmp(key, "validuntil") == 0) tkt->valid_until = atoi(value); else if (strcmp(key, "graceperiod") == 0) tkt->grace_period = atoi(value); else if (strcmp(key, "tokens") == 0) strncpy(tkt->tokens, value, sizeof(tkt->tokens)-1); else if (strcmp(key, "udata") == 0) strncpy(tkt->user_data, value, sizeof(tkt->user_data)-1); else if (strcmp(key, "bauth") == 0) strncpy(tkt->bauth, value, sizeof(tkt->bauth)-1); else if (strcmp(key, "multifactor") == 0) tkt->multifactor = atoi(value); } if (!tkt->uid[0] || tkt->valid_until == 0) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r, "TKT parse_ticket missing keys in ticket '%s'", ticket); return 0; } if (conf->debug >= 1) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT parse_ticket decoded ticket: uid %s, cip %s, validuntil %u, graceperiod %u, tokens %s, udata %s, bauth %s, multifactor %u", tkt->uid, tkt->clientip, tkt->valid_until, tkt->grace_period, tkt->tokens, tkt->user_data, tkt->bauth, tkt->multifactor); } return 1; } /* Search cookie headers for our ticket */ static int cookie_match(void *result, const char *key, const char *cookie) { cookie_res *cr = (cookie_res*)result; auth_pubtkt_dir_conf *conf = ap_get_module_config(cr->r->per_dir_config, &auth_pubtkt_module); if (cookie != NULL) { char *cookie_name, *value; size_t cknamelen = strlen(cr->cookie_name); cookie_name = apr_palloc(cr->r->pool, cknamelen + 2); strncpy(cookie_name, cr->cookie_name, cknamelen); cookie_name[cknamelen] = '='; cookie_name[cknamelen + 1] = '\0'; value = (char*)cookie; while ((value = strstr(value, cookie_name))) { /* Cookie includes our cookie_name - copy (first) value into cookiebuf */ char *cookiebuf, *end; size_t len; value += (cknamelen + 1); cookiebuf = apr_pstrdup(cr->r->pool, value); end = ap_strchr(cookiebuf, ';'); if (end) *end = '\0'; /* Ignore anything after the next ; */ /* Skip empty cookies (such as with misconfigured logoffs) */ len = strlen(cookiebuf); if (len > 0) { int i; /* UAs may quote cookie values */ if (cookiebuf[len-1] == '"') cookiebuf[len-1] = 0; if (cookiebuf[0] == '"') cookiebuf++; /* Replace '+' by ' ' (not handled by ap_unescape_url_keep2f) */ for (i = 0; cookiebuf[i]; i++) { if (cookiebuf[i] == '+') cookiebuf[i] = ' '; } /* URL-unescape cookie */ #ifdef APACHE24 if (ap_unescape_url_keep2f(cookiebuf, 1) != 0) { #else if (ap_unescape_url_keep2f(cookiebuf) != 0) { #endif ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, cr->r, "TKT cookie_match: error while URL-unescaping cookie"); continue; } cr->cookie = cookiebuf; if (conf->debug >= 1) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cr->r, "TKT cookie_match: found '%s'", cookiebuf); } return 0; } } } if (conf->debug >= 2) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cr->r, "TKT cookie_match: NOT found"); } return 1; } /* Look for a ticket in a header - maybe a cookie, maybe not */ static char *get_header_ticket(request_rec *r) { auth_pubtkt_dir_conf *conf = ap_get_module_config(r->per_dir_config, &auth_pubtkt_module); char *ticket_decoded, *found_ticket; const char *header_name, *header_list_str, *header_value; size_t ticket_len; header_list_str = (conf->auth_header_name) ? conf->auth_header_name : MOD_AUTH_PUBTKT_HEADER_NAME; if( !*header_list_str ) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, "TKTAuthHeader directive is empty" ); return NULL; } if (conf->debug >= 1) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT get_header_ticket: looking for ticket in '%s' header(s)", header_list_str ); } while( *header_list_str && (header_name = ap_getword_conf(r->pool, &header_list_str)) ) { if(!header_name) { break; } if (conf->debug >= 1) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT get_header_ticket: inspecting header '%s'", header_name ); } if( strcasecmp(header_name,"Cookie") == 0 ) { found_ticket = get_cookie_ticket(r); } else { header_value = apr_table_get(r->headers_in,header_name); if(!header_value) { if (conf->debug >= 1) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT get_header_ticket: auth header missing" ); } continue; } ticket_decoded = apr_pstrdup(r->pool,header_value); ticket_len = strlen(ticket_decoded); if( ticket_len <= 0 ) { if (conf->debug >= 1) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT get_header_ticket: auth header present but empty" ); } continue; } int i; /* Replace '+' by ' ' (not handled by ap_unescape_url_keep2f) */ for (i = 0; ticket_decoded[i]; i++) { if (ticket_decoded[i] == '+') ticket_decoded[i] = ' '; } /* URL-unescape cookie */ #ifdef APACHE24 if (ap_unescape_url_keep2f(ticket_decoded, 1) != 0) { #else if (ap_unescape_url_keep2f(ticket_decoded) != 0) { #endif ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r, "TKT get_header_ticket: error while URL-unescaping cookie"); continue; } if (conf->debug >= 1) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT get_header_ticket: found '%s'", ticket_decoded); } found_ticket = apr_pstrdup(r->pool, ticket_decoded); } if( found_ticket != NULL ) { if (conf->debug >= 1) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT get_header_ticket: found ticket in header '%s'", header_name); } return found_ticket; } } return NULL; } /* Look for a cookie ticket */ static char *get_cookie_ticket(request_rec *r) { auth_pubtkt_dir_conf *conf = ap_get_module_config(r->per_dir_config, &auth_pubtkt_module); /* Walk cookie headers looking for matching ticket */ cookie_res *cr = apr_palloc(r->pool, sizeof(*cr)); cr->r = r; cr->cookie = NULL; cr->cookie_name = (conf->auth_cookie_name) ? conf->auth_cookie_name : AUTH_COOKIE_NAME; apr_table_do(cookie_match, (void*)cr, r->headers_in, "Cookie", NULL); /* Give up if cookie not found or too short */ if (!cr->cookie || strlen(cr->cookie) < MIN_AUTH_COOKIE_SIZE) return NULL; return cr->cookie; } /* Validate the signature on this ticket, and if it is good, parse the ticket * Returns the parsed ticket if valid, or NULL otherwise */ static auth_pubtkt* validate_parse_ticket(request_rec *r, char *ticket) { auth_pubtkt_dir_conf *conf = ap_get_module_config(r->per_dir_config, &auth_pubtkt_module); char *sigptr, *sig_buf; char *tktval_buf; int sig_len; auth_pubtkt *tkt; EVP_MD_CTX *ctx; if (strlen(ticket) > MAX_TICKET_SIZE) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, "TKT validate_parse_ticket: ticket too long"); return NULL; } tkt = (auth_pubtkt*)apr_pcalloc(r->pool, sizeof(*tkt)); if (tkt == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, "TKT validate_parse_ticket: cannot allocate memory for ticket"); return NULL; } /* first check the cache for an entry for this ticket */ if (cache_get(ticket, tkt)) { if (conf->debug >= 1) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT validate_parse_ticket: found ticket in cache: '%s'", ticket); } return tkt; } /* Before we attempt to do any more sophisticated parsing, verify that the signature on the ticket is valid */ /* find the signature */ sigptr = strstr(ticket, ";sig="); if (sigptr == NULL) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r, "TKT validate_parse_ticket: no signature found in ticket"); return NULL; } /* split ticket value and signature */ tktval_buf = apr_pstrndup(r->pool, ticket, (sigptr - ticket)); sigptr += 5; sig_buf = (char*)apr_palloc(r->pool, strlen(sigptr) + 1); sig_len = apr_base64_decode(sig_buf, sigptr); if (sig_len <= 0) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r, "TKT validate_parse_ticket: empty or bad signature found in ticket"); return NULL; } if (conf->debug >= 1) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT validate_parse_ticket: tktval '%s', sig '%s'", tktval_buf, sigptr); } ERR_clear_error(); ctx = EVP_MD_CTX_new(); if (!EVP_VerifyInit(ctx, conf->digest)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r, "TKT validate_parse_ticket: EVP_VerifyInit failed"); EVP_MD_CTX_free(ctx); return NULL; } if (!EVP_VerifyUpdate(ctx, tktval_buf, strlen(tktval_buf))) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r, "TKT validate_parse_ticket: EVP_VerifyUpdate failed"); EVP_MD_CTX_free(ctx); return NULL; } if (EVP_VerifyFinal(ctx, (unsigned char*)sig_buf, sig_len, conf->pubkey) != 1) { unsigned long lasterr; char *errbuf = apr_palloc(r->pool, 120); ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r, "TKT validate_parse_ticket: invalid signature!"); while ((lasterr = ERR_get_error()) != 0) { ERR_error_string_n(lasterr, errbuf, 120); ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r, "TKT validate_parse_ticket: OpenSSL error: %s", errbuf); } EVP_MD_CTX_free(ctx); return NULL; } EVP_MD_CTX_free(ctx); /* good signature - parse ticket */ if (!parse_ticket(r, tktval_buf, tkt)) return NULL; /* put the parsed ticket into the cache */ cache_put(ticket, tkt); return tkt; } /* Check for required auth tokens * Returns 1 on success, 0 on failure */ static int check_tokens(request_rec *r, auth_pubtkt *tkt) { auth_pubtkt_dir_conf *conf = ap_get_module_config(r->per_dir_config, &auth_pubtkt_module); char *next_parsed_token; const char *t = NULL; int match = 0; /* Success if no tokens required */ if (conf->auth_token->nelts == 0 || strcmp(((char**)conf->auth_token->elts)[0], "NULL") == 0) return 1; /* Failure if no user tokens found */ if (!tkt->tokens[0]) return 0; t = apr_pstrdup(r->pool, tkt->tokens); while (*t && (next_parsed_token = ap_getword(r->pool, &t, ','))) { char** auth_tokens = (char **)conf->auth_token->elts; int i; for (i = 0; i < conf->auth_token->nelts; i++) { size_t token_len = strlen(auth_tokens[i]); if (strncmp(auth_tokens[i], next_parsed_token, token_len) == 0 && next_parsed_token[token_len] == 0) { match = 1; break; } } if (match) break; } if (conf->debug >= 1 && !match) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT: no matching tokens! (user tokens '%s')", tkt->tokens); } return match; } /* Check client IP address against the one found in the ticket (if any) Returns 1 on success, 0 on failure */ static int check_clientip(request_rec *r, auth_pubtkt *tkt) { if (!tkt->clientip[0]) return 1; /* no clientip in ticket */ #if AP_MODULE_MAGIC_AT_LEAST(20111130,0) return (strcmp(tkt->clientip, r->useragent_ip) == 0); #else return (strcmp(tkt->clientip, r->connection->remote_ip) == 0); #endif } /* Check whether the given ticket has timed out * Returns 1 if okay, 0 if timed out */ static int check_timeout(request_rec *r, auth_pubtkt *tkt) { time_t now = time(NULL); return (now <= tkt->valid_until); } /* Check whether the given ticket will time out and enter into grace period * Returns 1 if okay, 0 if timed out */ static int check_grace_period(request_rec *r, auth_pubtkt *tkt) { time_t now = time(NULL); return ((tkt->grace_period == 0 ) || (now <= tkt->grace_period)); } static int check_multifactor(request_rec *r, auth_pubtkt *tkt) { auth_pubtkt_dir_conf *conf = ap_get_module_config(r->per_dir_config, &auth_pubtkt_module); if (conf->require_multifactor == 1) { return (tkt->multifactor == 1); } return 1; } /* Hex conversion, from httpd util.c */ static const char c2x_table[] = "0123456789abcdef"; static APR_INLINE unsigned char *c2x(unsigned what, unsigned char *where) { #if APR_CHARSET_EBCDIC what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what); #endif /*APR_CHARSET_EBCDIC*/ *where++ = '%'; *where++ = c2x_table[what >> 4]; *where++ = c2x_table[what & 0xf]; return where; } /* Extra escaping - variant of httpd util.c ap_escape_path_segment */ static char *escape_extras(apr_pool_t *p, const char *segment) { char *copy = apr_palloc(p, 3 * strlen(segment) + 1); const unsigned char *s = (const unsigned char *)segment; unsigned char *d = (unsigned char *)copy; unsigned c; while ((c = *s)) { if (c == '=' || c == '&' || c == ':') d = c2x(c, d); else *d++ = c; ++s; } *d = '\0'; return copy; } /* External redirect to the given url, setting 'back' argument */ static int redirect(request_rec *r, char *location) { auth_pubtkt_dir_conf *conf = ap_get_module_config(r->per_dir_config, &auth_pubtkt_module); char *back_arg_name = (conf->back_arg_name) ? conf->back_arg_name : BACK_ARG_NAME; char *query; char *url, *back; const char *hostinfo = 0; int port; char sep; /* Get the scheme we use (http or https) */ const char *scheme = (char*)ap_http_method(r); /* Use main request args if subrequest */ request_rec *r_main = r->main == NULL ? r : r->main; if (r_main->args == NULL) query = ""; else query = apr_psprintf(r->pool, "?%s", r_main->args); if (!location) { return HTTP_FORBIDDEN; } /* Build back URL */ /* Use X-Forward-Host header for host:port info if available */ /* Failing that, use Host header */ hostinfo = apr_table_get(r->headers_in, "X-Forwarded-Host"); /*if (!hostinfo) XXX Host header doesn't include port?? hostinfo = apr_table_get(r->headers_in, "Host");*/ if (!hostinfo) { /* Fallback to using r->hostname and the server port. This usually works, but behind a reverse proxy the port may well be wrong. On the other hand, it's really the proxy's problem, not ours. */ port = ap_get_server_port(r); hostinfo = port == apr_uri_default_port_for_scheme(scheme) ? apr_psprintf(r->pool, "%s", r->hostname) : apr_psprintf(r->pool, "%s:%d", r->hostname, port); } back = apr_psprintf(r->pool, "%s://%s%s%s", scheme, hostinfo, r->uri, query); if (conf->debug >= 1) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT: back url '%s'", back); } /* Escape testing */ back = ap_escape_path_segment(r->pool, back); back = escape_extras(r->pool, back); /* Add a back url argument to url */ sep = ap_strchr(location, '?') ? '&' : '?'; url = apr_psprintf(r->pool, "%s%c%s=%s", location, sep, back_arg_name, back); if (conf->debug >= 2) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT: redirect '%s'", url); } apr_table_setn(r->headers_out, "Location", url); return (r->proto_num >= HTTP_VERSION(1,1)) ? HTTP_TEMPORARY_REDIRECT : HTTP_MOVED_TEMPORARILY; } /* ----------------------------------------------------------------------- */ /* Debug routines */ void dump_config(request_rec *r) { auth_pubtkt_dir_conf *conf = ap_get_module_config(r->per_dir_config, &auth_pubtkt_module); if (conf->debug >= 3) { /* Dump config settings */ fprintf(stderr,"[ mod_auth_pubtkt config ]\n"); fprintf(stderr,"URI: %s\n", r->uri); fprintf(stderr,"Filename: %s\n", r->filename); fprintf(stderr,"directory: %s\n", conf->directory); fprintf(stderr,"TKTAuthLoginURL: %s\n", conf->login_url); fprintf(stderr,"TKTAuthTimeoutURL: %s\n", conf->timeout_url); fprintf(stderr,"TKTAuthPostTimeoutURL: %s\n", conf->post_timeout_url); fprintf(stderr,"TKTAuthUnauthURL: %s\n", conf->unauth_url); fprintf(stderr,"TKTAuthHeader: %s\n", conf->auth_header_name); fprintf(stderr,"TKTAuthCookieName: %s\n", conf->auth_cookie_name); fprintf(stderr,"TKTAuthBackArgName: %s\n", conf->back_arg_name); fprintf(stderr,"TKTAuthRefreshURL: %s\n", conf->refresh_url); fprintf(stderr,"TKTAuthBadIPURL: %s\n", conf->badip_url); fprintf(stderr,"TKTAuthRequireSSL: %d\n", conf->require_ssl); if (conf->auth_token->nelts > 0) { char ** auth_token = (char **) conf->auth_token->elts; int i; for (i = 0; i < conf->auth_token->nelts; i++) { fprintf(stderr, "TKTAuthToken: %s\n", auth_token[i]); } } fprintf(stderr,"TKTAuthDebug: %d\n", conf->debug); fprintf(stderr,"TKTAuthFakeBasicAuth: %d\n", conf->fake_basic_auth); fprintf(stderr,"TKTAuthPassthruBasicAuth: %d\n", conf->passthru_basic_auth); fprintf(stderr,"TKTAuthMultifactorURL: %s\n", conf->multifactor_url); fprintf(stderr,"TKTAuthRequireMultifactor: %d\n", conf->require_multifactor); fflush(stderr); } } /* ----------------------------------------------------------------------- */ /* Main ticket authentication */ static int auth_pubtkt_check(request_rec *r) { char *ticket; auth_pubtkt *parsed; auth_pubtkt_dir_conf *conf = ap_get_module_config(r->per_dir_config, &auth_pubtkt_module); const char *scheme = (char*)ap_http_method(r); const char *current_auth = (char*)ap_auth_type(r); char *url = NULL; dump_config(r); if (!current_auth || strcasecmp(current_auth, MOD_AUTH_PUBTKT_AUTH_TYPE)) { return DECLINED; } /* Module misconfigured unless public key set */ if (!conf->pubkey) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, "TKT: TKTAuthPublicKey missing"); return HTTP_INTERNAL_SERVER_ERROR; } /* Redirect/login if scheme not "https" and require_ssl is set */ if (conf->require_ssl > 0 && strcmp(scheme, "https") != 0) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r, "TKT: redirect/login - unsecured request, TKTAuthRequireSSL is on"); return redirect(r, conf->login_url); } /* Check for ticket in customer header or cookie */ ticket = get_header_ticket(r); if (ticket == NULL) { ap_log_rerror(APLOG_MARK, APLOG_INFO, APR_SUCCESS, r, "TKT: no ticket found - redirecting to login URL"); return redirect(r, conf->login_url); } /* Validate and parse ticket (or get it from cache) */ parsed = validate_parse_ticket(r, ticket); if (parsed == NULL) { ap_log_rerror(APLOG_MARK, APLOG_INFO, APR_SUCCESS, r, "TKT: invalid ticket found - redirecting to login URL"); return redirect(r, conf->login_url); } /* Ticket is valid, setup apache user */ /* Allows later ticket check failure logging to include user */ #ifdef APACHE13 r->connection->user = parsed->uid; #else r->user = parsed->uid; #endif /* Check client IP address (if present in ticket) */ if (!check_clientip(r, parsed)) { #if AP_MODULE_MAGIC_AT_LEAST(20111130,0) char *remote_ip = r->useragent_ip; #else char *remote_ip = r->connection->remote_ip; #endif /* Add an IP param to URL */ char *badip_url = conf->badip_url ? conf->badip_url : conf->login_url; char sep = ap_strchr(badip_url, '?') ? '&' : '?'; url = apr_psprintf(r->pool, "%s%cip=%s", badip_url, sep, remote_ip); ap_log_rerror(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, r, "TKT: client IP mismatch (ticket: %s, request: %s) - redirecting to badip URL", parsed->clientip, remote_ip); return redirect(r, url); } /* Valid ticket, check timeout - redirect/timed-out if so */ if (!check_timeout(r, parsed)) { ap_log_rerror(APLOG_MARK, APLOG_INFO, APR_SUCCESS, r, "TKT: ticket expired - redirecting to timeout URL"); /* Special timeout URL can be defined for POST requests */ if (strcmp(r->method, "POST") == 0 && conf->post_timeout_url) url = conf->post_timeout_url; else url = conf->timeout_url ? conf->timeout_url : conf->login_url; return redirect(r, url); } /* Attempt to refresh cookie if it will expires - redirect on get if so */ if ( !check_grace_period(r, parsed) && strcmp(r->method, "GET") == 0 ) { ap_log_rerror(APLOG_MARK, APLOG_INFO, APR_SUCCESS, r, "TKT: ticket grace period - redirecting to refresh URL"); return redirect(r, (conf->refresh_url ? conf->refresh_url : conf->login_url)); } /* Check Mulitfactor - redirect to MultifactorURL if missing */ if (!check_multifactor(r, parsed)) { ap_log_rerror(APLOG_MARK, APLOG_INFO, APR_SUCCESS, r, "TKT: multifactor required - redirecting to multifactor URL"); return redirect(r, conf->multifactor_url ? conf->multifactor_url : conf->login_url); } /* Check tokens - redirect/unauthorised if so */ if (!check_tokens(r, parsed)) return redirect(r, conf->unauth_url ? conf->unauth_url : conf->login_url); /* Setup apache auth_type, and environment variables */ #ifdef APACHE13 r->connection->ap_auth_type = MOD_AUTH_PUBTKT_AUTH_TYPE; #else r->ap_auth_type = MOD_AUTH_PUBTKT_AUTH_TYPE; #endif apr_table_set(r->subprocess_env, REMOTE_USER_ENV, parsed->uid); apr_table_set(r->subprocess_env, REMOTE_USER_DATA_ENV, parsed->user_data); apr_table_set(r->subprocess_env, REMOTE_USER_TOKENS_ENV, parsed->tokens); if (!apr_table_get(r->headers_in, "Authorization")) { if (conf->passthru_basic_auth > 0 && parsed->bauth[0]) { char *bauth; if (conf->debug >= 1) ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT: Adding passthru basic auth"); /* need to decrypt bauth? */ bauth = parsed->bauth; if (conf->passthru_basic_key != NULL) { EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); char *decoded, *decrypted; int len = 0, declen = 0; if (conf->debug >= 2) ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT: Decrypting passthru basic auth"); /* base64 decode first */ decoded = (char *) apr_palloc(r->pool, 1 + apr_base64_decode_len(parsed->bauth)); len = apr_base64_decode(decoded, parsed->bauth); if (len <= PASSTHRU_AUTH_IV_SIZE) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r, "TKT: Bad encrypted passthru length %d", len); } else { /* Decrypt */ int tlen; decrypted = (char*)apr_palloc(r->pool, len+1); EVP_CIPHER_CTX_init(ctx); EVP_DecryptInit(ctx, EVP_aes_128_cbc(), (unsigned char*)conf->passthru_basic_key, (unsigned char*)decoded); EVP_CIPHER_CTX_set_padding(ctx, 0); if (EVP_DecryptUpdate(ctx, (unsigned char*)decrypted, &declen, (unsigned char*)&decoded[PASSTHRU_AUTH_IV_SIZE], len - PASSTHRU_AUTH_IV_SIZE) != 1) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, "TKT: passthru decryption failed"); EVP_CIPHER_CTX_free(ctx); return HTTP_INTERNAL_SERVER_ERROR; } if (EVP_DecryptFinal(ctx, (unsigned char*)(decrypted + declen), &tlen) != 1) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, "TKT: passthru decryption failed"); EVP_CIPHER_CTX_free(ctx); return HTTP_INTERNAL_SERVER_ERROR; } EVP_CIPHER_CTX_free(ctx); decrypted[declen] = 0; if (conf->debug >= 3) ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT: Decrypted passthru auth is %s", decrypted); bauth = ap_pbase64encode(r->pool, decrypted); } } apr_table_set(r->headers_in, "Authorization", apr_pstrcat(r->pool, "Basic ", bauth, NULL)); } else if (conf->fake_basic_auth > 0) { if (conf->debug >= 1) ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "TKT: Adding fake basic auth"); apr_table_set(r->headers_in, "Authorization", apr_pstrcat(r->pool, "Basic ", ap_pbase64encode(r->pool, apr_pstrcat(r->pool, parsed->uid, ":password", NULL)), NULL)); } } return OK; } /* ----------------------------------------------------------------------- */ /* Setup main module data structure */ #ifdef APACHE13 /* Apache 1.3 style */ module MODULE_VAR_EXPORT auth_pubtkt_module = { STANDARD_MODULE_STUFF, auth_pubtkt_init, /* initializer */ create_auth_pubtkt_config, /* create per-dir config structures */ merge_auth_pubtkt_config, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ auth_pubtkt_cmds, /* table of config file commands */ NULL, /* handlers */ NULL, /* filename translation */ auth_pubtkt_check, /* check user_id */ NULL, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ NULL, /* fixups */ NULL, /* logger */ NULL, /* header parser */ auth_pubtkt_child_init, /* child_init */ NULL, /* child_exit */ NULL /* post read-request */ }; #else /* Apache 2.0 style */ /* Register hooks */ static void auth_pubtkt_register_hooks (apr_pool_t *p) { ap_hook_post_config(auth_pubtkt_init, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_check_user_id(auth_pubtkt_check, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_child_init(auth_pubtkt_child_init, NULL, NULL, APR_HOOK_FIRST); } /* Declare and populate the main module data structure */ module AP_MODULE_DECLARE_DATA auth_pubtkt_module = { STANDARD20_MODULE_STUFF, create_auth_pubtkt_config, /* create per-dir config structures */ merge_auth_pubtkt_config, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ auth_pubtkt_cmds, /* table of config file commands */ auth_pubtkt_register_hooks /* register hooks */ }; #endif mod_auth_pubtkt-0.13/src/mod_auth_pubtkt.h000077500000000000000000000126301336114244100207630ustar00rootroot00000000000000#ifndef MOD_AUTH_PUBTKT_H #define MOD_AUTH_PUBTKT_H 1 #ifndef _WIN32 #include #include #endif #include #include #include #include #include #include #include #include #include #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined (LIBRESSL_VERSION_NUMBER) #define EVP_MD_CTX_new EVP_MD_CTX_create #define EVP_MD_CTX_free EVP_MD_CTX_destroy #endif #include "httpd.h" #include "http_config.h" #include "http_log.h" #include "http_core.h" #include "http_protocol.h" #include "http_request.h" #include "ap_mmn.h" #if MODULE_MAGIC_NUMBER < 20010224 #include "ap_compat.h" #else #include "apr_lib.h" #include "apr_strings.h" #include "apr_uuid.h" #include "apr_base64.h" #include "apu_version.h" #endif #ifndef ap_http_method #define ap_http_method ap_http_scheme #endif #if APU_MAJOR_VERSION > 0 #define apr_uri_default_port_for_scheme apr_uri_port_of_scheme #endif #define MOD_AUTH_PUBTKT_AUTH_TYPE "mod_auth_pubtkt" #define MOD_AUTH_PUBTKT_HEADER_NAME "Cookie" #define AUTH_COOKIE_NAME "auth_pubtkt" #define BACK_ARG_NAME "back" #define REMOTE_USER_ENV "REMOTE_USER" #define REMOTE_USER_DATA_ENV "REMOTE_USER_DATA" #define REMOTE_USER_TOKENS_ENV "REMOTE_USER_TOKENS" #define MIN_AUTH_COOKIE_SIZE 64 /* the Base64-encoded signature alone is >= 64 bytes */ #define CACHE_SIZE 200 /* number of entries in ticket cache */ #define MAX_UID_SIZE 64 /* maximum length of uid */ #define MAX_TICKET_SIZE 1024 /* maximum length of raw ticket */ #define PASSTHRU_AUTH_KEY_SIZE 16 /* length of symmetric key for passthru basic auth encryption */ #define PASSTHRU_AUTH_IV_SIZE 16 #define PUBTKT_AUTH_VERSION "0.13" /* ----------------------------------------------------------------------- */ /* Per-directory configuration */ typedef struct { char *directory; char *login_url; char *timeout_url; char *post_timeout_url; char *unauth_url; char *auth_header_name; char *auth_cookie_name; char *back_arg_name; char *refresh_url; char *badip_url; apr_array_header_t *auth_token; int require_ssl; int debug; int fake_basic_auth; int grace_period; int passthru_basic_auth; EVP_PKEY *pubkey; /* public key for signature verification */ const EVP_MD *digest; /* TKTAuthDigest */ const char *passthru_basic_key; int require_multifactor; char *multifactor_url; } auth_pubtkt_dir_conf; /* Ticket structure */ typedef struct { char uid[MAX_UID_SIZE+1]; char clientip[40]; unsigned int valid_until; unsigned int grace_period; char bauth[256]; char tokens[256]; char user_data[256]; int multifactor; } auth_pubtkt; typedef struct { request_rec *r; char *cookie; char *cookie_name; } cookie_res; /* An entry in the ticket cache. Note that while each entry has a hash (over the ticket string), this is not a hash table; managing a real hash table without fiddling with pointers (which could become a problem if the cache was ever converted to use shared memory) is rather difficult, and before we start optimizing the scan over ~200 integer hash values, getting rid of some strlen()s would probably make a bigger difference. */ typedef struct { unsigned int hash; /* hash over the unparsed ticket value (0 = slot available) */ char ticket[MAX_TICKET_SIZE+1]; /* the unparsed ticket value */ auth_pubtkt tkt; } auth_pubtkt_cache_ent; typedef struct { auth_pubtkt_cache_ent slots[CACHE_SIZE]; int nextslot; } auth_pubtkt_cache; #ifdef APACHE13 void auth_pubtkt_init(server_rec *s, pool *p); void auth_pubtkt_child_init(server_rec *s, pool *p); #else static int auth_pubtkt_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s); static void auth_pubtkt_child_init(apr_pool_t *p, server_rec *s); #endif static void* create_auth_pubtkt_config(apr_pool_t *p, char* path); static void* merge_auth_pubtkt_config(apr_pool_t *p, void* parent_dirv, void* subdirv); static void cache_init(apr_pool_t *p, server_rec* s); static int cache_get(const char* ticket, auth_pubtkt *tkt); static void cache_put(const char *ticket, auth_pubtkt *tkt); static unsigned int cache_hash(const char *ticket); static const char *set_auth_pubtkt_token(cmd_parms *cmd, void *cfg, const char *param); static const char *setup_pubkey(cmd_parms *cmd, void *cfg, const char *param); static const char *setup_passthru_basic_key(cmd_parms *cmd, void *cfg, const char *param); static const char *set_auth_pubtkt_debug(cmd_parms *cmd, void *cfg, const char *param); static int parse_ticket(request_rec *r, char *ticket, auth_pubtkt *tkt); static int cookie_match(void *result, const char *key, const char *cookie); static char *get_cookie_ticket(request_rec *r); static auth_pubtkt* validate_parse_ticket(request_rec *r, char *ticket); static int check_tokens(request_rec *r, auth_pubtkt *tkt); static int check_clientip(request_rec *r, auth_pubtkt *tkt); static int check_timeout(request_rec *r, auth_pubtkt *tkt); static int check_grace_period(request_rec *r, auth_pubtkt *tkt); static APR_INLINE unsigned char *c2x(unsigned what, unsigned char *where); static char *escape_extras(apr_pool_t *p, const char *segment); static int redirect(request_rec *r, char *location); void dump_config(request_rec *r); static int auth_pubtkt_check(request_rec *r); #endif