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 is 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.

I still have some minor things to work out. I now use firstname + lastname 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...

Let me think about it for a while and you are welcome with suggestions?

As you can see, there is some glitches in this documentation, but I will try to add more!

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'); $ssl_map_info = "true"; $olderror = error_reporting(E_ALL ^ E_NOTICE); $ssl_RN = strtolower($_SERVER['SSL_CLIENT_S_DN']); $search = array ('/Ã¥/i', '/Ã¤/i', '/Ã¶/i', '/Ã©/i'); $replace = array ('a', 'a', 'o', 'e'); $firstname = $_SERVER['SSL_CLIENT_S_DN_G']; $firstname = preg_replace($search, $replace, $firstname); $lastname = $_SERVER['SSL_CLIENT_S_DN_S']; $lastname = preg_replace($search, $replace, $lastname); $ssl_UN = ucfirst(strtolower($firstname)). ' '         . ucfirst(strtolower($lastname)); $ssl_email = strtolower($firstname . '.' . $lastname . '@yourdomain'); error_reporting($olderror); SSLAuthSetup;
 * 1) SSL Authentication Stuff
 * 1) Load SSLAuthPlugin
 * 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
 * 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 from Certificate 'will' make collisions... but what to use?
 * 2) UN could be md5-hash of DN, but its ugly to use...
 * 1) Map e-mail to something close?
 * 1) Turn error reporting back on
 * 1) Activate SSL Plugin

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

<?php /** * Version 1.0.1 (Works out of box with MW 1.7.1) * * 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 Martin Johnson * Portions Copyright 2006, 2007 Regents of the University of California * Portions Copyright 2007 Steven Langenaken * Released under the GNU General Public License * * 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) return 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); //For security, scramble the password to confuse the enemy. //This set the password to a 15 byte random string. $pass = null; for($i = 0; $i < 15; ++$i) $pass .= chr(mt_rand(0,255)); $user->setPassword($pass); return true; }    /**      * See AuthPlugin.php for specific information */    function autoCreate { return true; }    /**      * See AuthPlugin.php for specific information */   function allowPasswordChange { global $ssl_pretend;

if($ssl_pretend) return true; else return false; }

/**     * See AuthPlugin.php for specific information */   function setPassword( $password ) { global $ssl_pretend;

if($ssl_pretend) return true; else 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 ) { 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.0',    '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; global $ssl_pretend;

$ssl_pretend = false; 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; global $ssl_pretend; //Give us a user, see if we're around $tmpuser = User::LoadFromSession; //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->setCookies; $wgUser->setupSession; 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 == 'AutoAuth') $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($user);

//And now we clean up our hack if($wgLangUnset == true) {           unset($wgLang); unset($wgLangUnset); }

//The mediawiki developers entirely broke use of this the //straightforward way in 1.9, so now we just lie... $ssl_pretend = true;

//Now we _do_ the black magic $lf->mRemember = false; $lf->initUser(&$wgUser);

//Stop pretending now $ssl_pretend = false; //Finish it off $wgUser->saveSettings; $wgUser->setupSession; $wgUser->setCookies; }

/* Puts the auto-auth hook back into the hooks array */ function BringBackAA(&$user) {       global $wgHooks;

foreach ($wgHooks['AutoAuthenticate'] as $key => $value) {           if($value == 'BringBackAA') $wgHooks['AutoAuthenticate'][$key] = 'AutoAuth'; } }

?>