Extension:SSL authentication

SSL Authentication is an extension that autologon users with their SSL certificate. It uses mod_ssl in Apache to fetch DN from client certificate and map that to my MediaWiki name. All users will autologon and all users are required to use certificates.

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 Shibboleth Authentication by Djcapelis and wow! That made it easy to rewrite my code to an extension and upgrade to the latest Mediawiki version.

Over time, more people have been working and 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 user certificate) to make loginname and uses DN for real name, byt firstname lastname is probably not unique in a larger environment, DN os but it's not uasble as username in MW. Maybe an md5 hash of DN, but then, it's ugly as a username... I think you know the best way to solve this in your own environment.

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

Clientside certificate and SSL
describe what clientside certificate and SSL is

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 smartcard 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  Options None AllowOverride None Order allow,deny Allow from all SSLRequireSSL SSLRequire %{SSL_CLIENT_S_DN}  =~ m/.*serialNumber= $/ 

We use SSLRequire to restrict usage of our wiki to just some users, with certificates enrolled by our CA. 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.

LocalSettings.php
Add this to your LocalSettings.php to init the extension

require_once('extensions/SSLAuthPlugin.php');
 * 1) Load SSLAuthPlugin

$ssl_map_info = true;
 * 1) Feel free to use extra PHP code to munge the variables if you'd like
 * 2) Additionally if you wish to only map some of the name data, set this to true
 * 3) and either blank ssl_RN and ssl_email or comment them out entirely.


 * 1) Ssssh.... quiet down errors
 * 2) $olderror = error_reporting(E_ALL ^ E_NOTICE);

$ssl_RN = $_SERVER['SSL_CLIENT_S_DN'];
 * 1) Map Real Name from certificate
 * 2) Can be DN but is it right?


 * 1) MW username is required to map to something
 * 2) You should beware of possible namespace collisions, it is best to chose
 * 3) something that will not violate MW's usual restrictions on characters


 * 1) Just using Firstname + Lastname (CN) from Certificate 'will' make collisions... but what to use?
 * 2) UN could be md5-hash of DN, but its ugly to use...

$ssl_UN = $_SERVER['SSL_CLIENT_S_DN_CN'];

if ($_SERVER['SSL_CLIENT_S_DN_Email'] != '') $ssl_email = $_SERVER['SSL_CLIENT_S_DN_Email']; else $ssl_email = strtolower($firstname . '.' . $lastname . '@ds.se');
 * 1) Map e-mail to something close?


 * 1) Turn error reporting back on
 * 2) error_reporting($olderror);

SSLAuthSetup;
 * 1) Activate SSL Plugin

SSLAuthPlugin.php
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; global $wgUser; global $wgContLang; global $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; global $wgContLang; global $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'; } }

?>