Extension:SSL authentication

From MediaWiki.org

Jump to: navigation, search
Manual on MediaWiki Extensions
List of MediaWiki Extensions
SSL Authentication

Release status: stable

Implementation User identity, Special page
Description Automagic login with certificates using Apache2 mod_ssl clientside
Author(s) Martin Johnson
Last Version 1.1.2 (2008-06-10)
MediaWiki 1.10.0, 1.11.0, 1.12.0
License No license specified
Download this page

SSL Authentication is an extension that automatically logs users into the wiki using their SSL certificate. It uses mod_ssl in Apache to fetch the DN from the client certificate and maps that to a MediaWiki user name. All users are automatically logged in, and all users are required to use certificates. These certificates must be vouched for by one of the certification authorities on file, specified by SSLCACertificateFile option. Wiki user names are taken from the user's certificate (SSL_CLIENT_S_DN_CN), and if that user name does not already exist, it is created.

I started this work for MediWiki version 1.5.3 and we have used it for some months. A couple of weeks ago, I discovered Extension:Shibboleth Authentication] by Djcapelis and wow! That made it easy to rewrite my code as an extension and upgrade to the latest MediaWiki version.

Over time more people have been contributing, and in particular I want to say thanks to Krzysztof Kozlowski and D.J. Capelis for their help.

I still have some minor things to work out. I now use firstname + lastname (or CN in the user certificate) to as the login name and uses DN for the real name, but firstname + lastname is probably not unique in a larger environment, DN should be but it's not usable as a user name in MW. Maybe an md5 hash of the DN, but that makes for an ugly user name... You probably know the best way to resolve this for your own environment.

As you can see, there are some glitches in this documentation and you are welcome to help. :-)

Contents

[edit] Clientside certificate and SSL

describe what clientside certificate and SSL is

[edit] Configure Apache

For a start, you need some prerequisites. First, you need certificates for all your users. Take a look at OpenCA or the swedish PrimeKey Solutions if you don't have certificates. Maybe windowscertificates can be used?

We use smartcards for all our users.

Then you need to configure your Apache to use SSL. This is my no-comments code for httpd.conf to setup this:

SSLEngine on
SSLProtocol -all +TLSv1 +SSLv3
SSLCipherSuite HIGH:MEDIUM
SSLProxyEngine off
SSLCertificateFile /etc/apache2/ssl.crt/server.crt
SSLCertificateKeyFile /etc/apache2/ssl.key/server.key
SSLCertificateChainFile /etc/apache2/ssl.crt/ca.crt
SSLCACertificateFile /etc/apache2/ssl.crt/ca-dskort.crt
SSLOptions +StrictRequire +OptRenegotiate +StdEnvVars +ExportCertData

SSLVerifyClient require
SSLVerifyDepth 1

SetEnvIf User-Agent ".*MSIE.*" \
    nokeepalive ssl-unclean-shutdown \
    downgrade-1.0 force-response-1.0

CustomLog /var/log/apache2/ssl_request_log   ssl_combined

<Directory "/srv/www/htdocs/wiki/">
    Options None
    AllowOverride None
    Order allow,deny
    Allow from all
    SSLRequireSSL
    SSLRequire  %{SSL_CLIENT_S_DN}  =~ m/.*serialNumber=<personnummer>$/
</Directory>

We use SSLRequire to restrict usage of our wiki to certain users, with certificates enrolled by our CA which is stored in SSLCACertificateFile. Find some unique thing or add all users DN in this list. If you have used SSL and client certificates, you know what to do.

[edit] LocalSettings.php

Add this to your LocalSettings.php to init the extension

#Load SSLAuthPlugin
require_once('extensions/SSLAuthPlugin.php');
 
#Feel free to use extra PHP code to munge the variables if you'd like
#Additionally if you wish to only map some of the name data, set this to true
#and either blank ssl_RN and ssl_email or comment them out entirely.
$ssl_map_info = true;
 
#Ssssh.... quiet down errors
#$olderror = error_reporting(E_ALL ^ E_NOTICE);
 
#Map Real Name from certificate
#Can be DN but is it right?
$ssl_RN = $_SERVER['SSL_CLIENT_S_DN'];
 
#MW username is required to map to something
#You should beware of possible namespace collisions, it is best to chose
#something that will not violate MW's usual restrictions on characters
 
#Just using Firstname + Lastname (CN) from Certificate 'will' make collisions... but what to use?
#UN could be md5-hash of DN, but its ugly to use...
 
$ssl_UN = $_SERVER['SSL_CLIENT_S_DN_CN'];
 
#Map e-mail to something close?
if ($_SERVER['SSL_CLIENT_S_DN_Email'] != '')
	$ssl_email = $_SERVER['SSL_CLIENT_S_DN_Email'];
else
	$ssl_email = strtolower($firstname . '.' . $lastname . '@ds.se');
 
#Turn error reporting back on
#error_reporting($olderror);
 
#Activate SSL Plugin
SSLAuthSetup();

[edit] SSLAuthPlugin.php (up to MW 1.10)

Copypaste this code to the new file extensions/SSLAuthPlugin.php

<?php
 
/**
 * Version 1.1 (Works out of box with MW 1.10.0)
 *
 * Authentication Plugin for Apache2 mod_ssl
 * Derived from AuthPlugin.php and
 * http://meta.wikimedia.org/wiki/Shibboleth_Authentication
 *
 * Much of the commenting comes straight from AuthPlugin.php
 *
 * Portions Copyright 2006, 2007 Martin Johnson
 * Portions Copyright 2006, 2007 Regents of the University of California
 * Portions Copyright 2007 Steven Langenaken
 * Portions Copyright 2007 Krzysztof Kozlowski
 * Released under the GNU General Public License
 *
 * Changes between 1.1 and 1.0.2:
 * == 1.10 compatibility fixes
 
 * Changes between 1.0.2 and 1.0.1:
 * = Merge changes from Shibboleth Authentication: (By DJC)
 * == More 1.9 compatibility fixes and less ugly code
 *
 * Changes between 1.0.1 and 1.0:
 * = Merge changes from Shibboleth Authentication: (By DJC)
 * == Compatible with MW 1.9+ again (By DJC)
 * == Minor fix in loginform handling (By Steven Langenaken)
 *
 * Documentation at http://www.mediawiki.org/wiki/Extension:SSL_authentication
 */
 
require_once('AuthPlugin.php');
 
class SSLAuthPlugin extends AuthPlugin {
	/**
	 * See AuthPlugin.php for specific information
	 */
	function userExists( $username ) {
		return true;
	}
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function authenticate( $username, $password ) {
		global $ssl_UN;
 
		if($username == $ssl_UN)
			return true;
		else
			return false;
	}
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function modifyUITemplate( &$template ) {
		$template->set( 'usedomain', false );
	}
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function setDomain( $domain ) {
		$this->domain = $domain;
	}
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function validDomain( $domain ) {
		return true;
	}
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function updateUser( &$user ) {
		global $ssl_map_info;
		global $ssl_email;
		global $ssl_RN;
 
		//Map extra info or not?
		if($ssl_map_info == true) {
			//If Email, set info in MW
			if($ssl_email != null)
			$user->setEmail($ssl_email);
 
			//If realName, set info in MW
			if($ssl_RN != null)
			$user->setRealName($ssl_RN);
 
			$user->saveSettings();
		}
 
 
		//For security, set password to a non-existant hash.
		$user->load();
		if($user->mPassword != "nologin") {
			$user->mPassword = "nologin";
			$user->saveSettings();
		}
 
		return true;
	}
 
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function autoCreate() {
		return true;
	}
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function allowPasswordChange() {
		return false;
	}
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function setPassword( $password ) {
		return false;
	}
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function updateExternalDB( $user ) {
		//Not really, but wiki thinks we did...
		return true;
	}
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function canCreateAccounts() {
		return false;
	}
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function addUser( $user, $password, $email='', $realname='' ) {
		return false;
	}
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function strict() {
		return false;
	}
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function initUser( &$user ) {
		//Update MW with new user information
		$this->updateUser($user);
	}
 
	/**
	 * See AuthPlugin.php for specific information
	 */
	function getCanonicalName( $username ) {
		return $username;
	}
}
 
/**
 * End of AuthPlugin Code, beginning of hook code and auth functions
 */
 
/**
 * Some extension information init
 */
$wgExtensionFunctions[] = 'SSLAuthSetup';
$wgExtensionCredits['other'][] = array(
	'name' => 'SSLAuth',
	'version' => '1.1',
	'author' => 'Martin Johnson',
	'description' => 'Automagic login with certificates using Apache2 mod_ssl clientside',
	'url' => 'http://www.mediawiki.org/wiki/Extension:SSL_authentication'
);
 
/**
 * Setup extensionfunctions
 */
function SSLAuthSetup() {
	global $ssl_UN;
	global $wgHooks;
	global $wgAuth;
 
	if($ssl_UN != null) {
		$wgHooks['AutoAuthenticate'][] = 'SSLAuth'; /* Hook for magical authN */
		$wgHooks['PersonalUrls'][] = 'NoLogout'; /* Disallow logout link */
		$wgAuth = new SSLAuthPlugin();
	}
 /**
  * Hooks looks funny in Special:Version
  * Written twice. Whats wrong with this code?
  */
}
 
/* No logout link in MW */
function NoLogout(&$personal_urls, $title) {
	$personal_urls['logout'] = null;
}
 
/* Tries to be magical about when to log in users and when not to. */
function SSLAuth(&$user) {
	global $ssl_UN, $wgUser, $wgContLang, $wgHooks;
 
	//Give us a user, see if we're around
	//$tmpuser = User::LoadFromSession(); // Pre MediaWiki 1.10
	$tmpuser = User::newFromSession(); // For MediaWiki 1.10.0 and up
 
	//They already with us?  If so, quit this function.
	if($tmpuser->isLoggedIn())
	return;
 
	//Is the user already in the database?
	$tmpuser = User::newFromName($ssl_UN);
 
	//If exists, log them in
	if($tmpuser->getID() != 0) {
		$wgUser = &$tmpuser;
		$wgUser->setupSession();
		$wgUser->setCookies();
		return;
	}
 
	//Okay, kick this up a notch then...
	$wgUser = &$tmpuser;
	$wgUser->setName($wgContLang->ucfirst($ssl_UN));
 
	/*
	 * Some magic that Shibboleth Authentication does and I just copy
	 */
	require_once('SpecialUserlogin.php');
 
	//This section contains a silly hack for MW
	global $wgLang, $wgContLang, $wgRequest;
	if(!isset($wgLang)) {
		$wgLang = $wgContLang;
		$wgLangUnset = true;
	}
 
	//Temporarily kill The AutoAuth Hook to prevent recursion
	foreach ($wgHooks['AutoAuthenticate'] as $key => $value) {
		if($value == 'SSLAuth')
		$wgHooks['AutoAuthenticate'][$key] = 'BringBackAA';
	}
 
	//This creates our form that'll do black magic
	$lf = new LoginForm($wgRequest);
 
	//Place the hook back (Not strictly necessarily MW Ver >= 1.9)
	BringBackAA();
 
	//And now we clean up our hack
	if($wgLangUnset == true) {
		unset($wgLang);
		unset($wgLangUnset);
	}
 
	//Now we _do_ the black magic
	$lf->mRemember = false;
	$lf->initUser($wgUser);
 
	//Finish it off
	$wgUser->saveSettings();
	$wgUser->setupSession();
	$wgUser->setCookies();
}
 
/* Puts the auto-auth hook back into the hooks array */
function BringBackAA() {
	global $wgHooks;
	foreach ($wgHooks['AutoAuthenticate'] as $key => $value) {
		if($value == 'BringBackAA')
		$wgHooks['AutoAuthenticate'][$key] = 'SSLAuth';
	}
}

[edit] SSLAuthPlugin.php (MW 1.11 upwards)

<?php
 
/**
 * Version 1.1.2 (Works out of box with MW 1.11.0 and MW 1.12.0)
 *
 * Authentication Plugin for Apache2 mod_ssl
 * Derived from AuthPlugin.php and
 * http://meta.wikimedia.org/wiki/Shibboleth_Authentication
 *
 * Much of the commenting comes straight from AuthPlugin.php
 *
 * Portions Copyright 2006, 2007 Martin Johnson
 * Portions Copyright 2006, 2007 Regents of the University of California
 * Portions Copyright 2007 Steven Langenaken
 * Portions Copyright 2007 Krzysztof Kozlowski
 * compatibility fixes for 1.11 / 1.12  by datenritter.de
 * Released under the GNU General Public License
 *
 * Changes between 1.2 and 1.1.2
 * == 1.11 compatibility fixes
 
 * Changes between 1.1 and 1.0.2:
 * == 1.10 compatibility fixes
 
 * Changes between 1.0.2 and 1.0.1:
 * = Merge changes from Shibboleth Authentication: (By DJC)
 * == More 1.9 compatibility fixes and less ugly code
 *
 * Changes between 1.0.1 and 1.0:
 * = Merge changes from Shibboleth Authentication: (By DJC)
 * == Compatible with MW 1.9+ again (By DJC)
 * == Minor fix in loginform handling (By Steven Langenaken)
 *
 * Documentation at http://www.mediawiki.org/wiki/Extension:SSL_authentication
 */
 
require_once('AuthPlugin.php');
 
class SSLAuthPlugin extends AuthPlugin {
        /**
         * See AuthPlugin.php for specific information
         */
        function userExists( $username ) {
                return true;
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function authenticate( $username, $password ) {
                global $ssl_UN;
 
                if($username == $ssl_UN)
                        return true;
                else
                        return false;
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function modifyUITemplate( &$template ) {
                $template->set( 'usedomain', false );
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function setDomain( $domain ) {
                $this->domain = $domain;
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function validDomain( $domain ) {
                return true;
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function updateUser( &$user ) {
                global $ssl_map_info;
                global $ssl_email;
                global $ssl_RN;
 
                //Map extra info or not?
                if($ssl_map_info == true) {
                        //If Email, set info in MW
                        if($ssl_email != null)
                        $user->setEmail($ssl_email);
 
                        //If realName, set info in MW
                        if($ssl_RN != null)
                        $user->setRealName($ssl_RN);
 
                        $user->saveSettings();
                }
 
 
                //For security, set password to a non-existant hash.
                $user->load();
                if($user->mPassword != "nologin") {
                        $user->mPassword = "nologin";
                        $user->saveSettings();
                }
 
                return true;
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function autoCreate() {
                return true;
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function allowPasswordChange() {
                return false;
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function setPassword( $password ) {
                return false;
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function updateExternalDB( $user ) {
                //Not really, but wiki thinks we did...
                return true;
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function canCreateAccounts() {
                return false;
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function addUser( $user, $password, $email='', $realname='' ) {
                return false;
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function strict() {
                return false;
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function initUser( &$user ) {
                //Update MW with new user information
                $this->updateUser($user);
        }
 
        /**
         * See AuthPlugin.php for specific information
         */
        function getCanonicalName( $username ) {
                return $username;
        }
}
 
/**
 * End of AuthPlugin Code, beginning of hook code and auth functions
 */
 
/**
 * Some extension information init
 */
$wgExtensionFunctions[] = 'SSLAuthSetup';
$wgExtensionCredits['other'][] = array(
	'name' => 'SSLAuth',
	'version' => '1.1',
	'author' => 'Martin Johnson',
	'description' => 'Automagic login with certificates using Apache2 mod_ssl clientside',
	'url' => 'http://www.mediawiki.org/wiki/Extension:SSL_authentication'
);
 
/**
 * Setup extensionfunctions
 */
function SSLAuthSetup() {
        global $ssl_UN, $wgHooks, $wgAuth;
 
        if($ssl_UN != null) {
                $wgHooks['AutoAuthenticate'][] = 'SSLAuth'; /* Hook for magical authN */
                $wgHooks['PersonalUrls'][] = 'NoLogout'; /* Disallow logout link */
                $wgAuth = new SSLAuthPlugin();
        }
 /**
  * Hooks looks funny in Special:Version
  * Written twice. Whats wrong with this code?
  */
}
 
/* No logout link in MW */
function NoLogout(&$personal_urls, $title) {
        $personal_urls['logout'] = null;
	return true;
}
 
/* Tries to be magical about when to log in users and when not to. */
function SSLAuth(&$user) {
        global $ssl_UN, $wgUser, $wgContLang, $wgHooks;
 
        //Give us a user, see if we're around
        //$tmpuser = User::LoadFromSession(); // Pre MediaWiki 1.10
        $tmpuser = User::newFromSession(); // For MediaWiki 1.10.0 and up
 
        //They already with us?  If so, quit this function.
        if($tmpuser->isLoggedIn())
        return true;
 
        //Is the user already in the database?
        $tmpuser = User::newFromName($ssl_UN);
 
        //If exists, log them in
        if($tmpuser->getID() != 0) {
                $wgUser = &$tmpuser;
                $wgUser->setupSession();
                $wgUser->setCookies();
                return true;
        }
 
        //Okay, kick this up a notch then...
        $wgUser = &$tmpuser;
        $wgUser->setName($wgContLang->ucfirst($ssl_UN));
 
        /*
         * Some magic that Shibboleth Authentication does and I just copy
         */
        require_once('SpecialUserlogin.php');
 
        //This section contains a silly hack for MW
        global $wgLang, $wgContLang, $wgRequest;
        if(!isset($wgLang)) {
                $wgLang = $wgContLang;
                $wgLangUnset = true;
        }
 
        //Temporarily kill The AutoAuth Hook to prevent recursion
        foreach ($wgHooks['AutoAuthenticate'] as $key => $value) {
                if($value == 'SSLAuth')
                $wgHooks['AutoAuthenticate'][$key] = 'BringBackAA';
        }
 
        //This creates our form that'll do black magic
        $lf = new LoginForm($wgRequest);
 
        //Place the hook back (Not strictly necessarily MW Ver >= 1.9)
        BringBackAA();
 
        //And now we clean up our hack
        if($wgLangUnset == true) {
                unset($wgLang);
                unset($wgLangUnset);
        }
 
        //Now we _do_ the black magic
        $lf->mRemember = false;
        $lf->initUser($wgUser, true);
 
        //Finish it off
        $wgUser->saveSettings();
        $wgUser->setupSession();
        $wgUser->setCookies();
}
 
/* Puts the auto-auth hook back into the hooks array */
function BringBackAA() {
        global $wgHooks;
        foreach ($wgHooks['AutoAuthenticate'] as $key => $value) {
                if($value == 'BringBackAA')
                $wgHooks['AutoAuthenticate'][$key] = 'SSLAuth';
        }
	return true;
}
Personal tools