diff --git a/modules/aselect/docs/aselect.txt b/modules/aselect/docs/aselect.txt
index 9e3f91cb1c70b56ade5840c7737923695470a911..756798e12fc3a23eb183a483b7e9f1eba29d816d 100644
--- a/modules/aselect/docs/aselect.txt
+++ b/modules/aselect/docs/aselect.txt
@@ -1,64 +1,44 @@
-Using the A-Select authentication source with simpleSAMLphp
-===========================================================
+A-Select module for simpleSAMLphp
+---------------------------------
 
-This authentication source for A-Select is based on the a-select
-handler by Hans Zandbelt. The original source combines the possibility
-to use an A-Select server as an authentication source and the possibility
-to use simpleSAMLphp as an A-Select server. This module only acts as a
-authentication source. Signing is not (yet) supported.
+This module allows one to use an A-Select server as authentication
+source for simpleSAMLphp.
 
-The structure applied follows the structure of the CAS authentication source.
+The module supports the A-Select protocol, including signing of
+requests. Not supported is A-Select Cross.
 
+Usage:
 
-Setting up the A-Select authentication module
-----------------------------------------------
+Enable the module if not already enabled:
+$ touch modules/aselect/enabled
 
-The first thing you need to do is to enable the aselect module:
+In config/authsources.php, configure your A-Selectserver as an
+authentication source. The following is an example for a source
+named 'aselect':
 
-    touch modules/aselect/enable
-
-The A-Select authentication module has two modes of operation.
-
-1. The module can act to the A-Select server as an application.
-
-Configuration in A-Select:
-
-        <application id="app1" level="30">
-            <attribute_policy>policyA</attribute_policy>
-            <forced_authenticate>false</forced_authenticate>
-        </application>
-
-Configuration in authsources.php
-
-    'example-aselect' => array(
+    'aselect' => array(
         'aselect:aselect',
-        'serverurl'            => 'http://a-select.dev.han.nl:8080/aselectserver/server',
-        'serverid'             => 'hanaselect',
-        'type'                 => 'app',                   # type = app/cross
-        'app_id'               => 'app1',                  # only if type = app
+        'app_id' => 'simplesamlphp',
+        'server_id' => 'sso.example.com',
+        'server_url' => 'https://test.sso.example.com/server',
+        'private_key' => 'file:///etc/ssl/private/aselect.key'
     ),
 
-2. The module can act to the A-Select server as cross A-Select.
+The parameters:
+- app_id: the application I for simpleSAMLphp as configured in
+  your A-Select server;
+- server_id: the A-Select server ID as configured in your
+  A-Select server;
+- server_url: the URL for your A-Selectserver, usually ends in
+  '/server/.
+- private_key: the key you want to use for signing requests.
+  If you're really sure you do not want request signing, you
+  can set this option to a null value.
+Options 'serverurl' and 'serverid' (without underscore) are
+supported for backwards compatibility.
 
-Configuration in A-Select:
-
-    <cross_aselect>
-        <local_servers require_signing="false">
-            <organization id="simpleSAMLphp" server="sso.testorg.com" attribute_policy="policyA">
-            </organization>
-        </local_servers>
-    </cross_aselect>
-
-
-Configuration in authsources.php
-
-    'example-aselect' => array(
-        'aselect:aselect',
-        'serverurl'            => 'http://a-select.dev.han.nl:8080/aselectserver/server',
-        'serverid'             => 'hanaselect',
-        'type'                 => 'cross',                 # type = app/cross
-        'local_organization'   => 'simpleSAMLphp',         # only if type = cross
-        'required_level'       => 10,                      # only if type = cross, defaults to 10
-    ),
+Author: Wessel Dankers <wsl@uvt.nl>
 
+Copyright: © 2011,2012 Tilburg University (http://www.tilburguniversity.edu)
 
+License: GPL version 3 or any later version.
diff --git a/modules/aselect/lib/Auth/Source/aselect.php b/modules/aselect/lib/Auth/Source/aselect.php
index 2cba46470614d2277d8ccc65bb4536a501cade3d..c7cb88d2f789b7a370a6c8924c760dc4ee8bf531 100644
--- a/modules/aselect/lib/Auth/Source/aselect.php
+++ b/modules/aselect/lib/Auth/Source/aselect.php
@@ -1,30 +1,15 @@
 <?php
 
 /**
- * A-Select authentication source.
+ * Authentication module which acts as an A-Select client
  *
- * Based on www/aselect/handler.php by Hans Zandbelt, SURFnet BV. <hans.zandbelt@surfnet.nl>
- *
- * @author Patrick Honing, Hogeschool van Arnhem en Nijmegen. <Patrick.Honing@han.nl>
- * @package simpleSAMLphp
- * @version $Id$
+ * @author Wessel Dankers, Tilburg University
  */
 class sspmod_aselect_Auth_Source_aselect extends SimpleSAML_Auth_Source {
-
-	/**
-	 * The string used to identify our states.
-	 */
-	const STAGE_INIT = 'aselect:init';
-
-	/**
-	 * The key of the AuthId field in the state.
-	 */
-	const AUTHID = 'aselect:AuthId';
-
-	/**
-	 * @var array with aselect configuration
-	 */
-	private $asconfig;
+	private $app_id = 'simplesamlphp';
+	private $server_id;
+	private $server_url;
+	private $private_key;
 
 	/**
 	 * Constructor for this authentication source.
@@ -33,142 +18,186 @@ class sspmod_aselect_Auth_Source_aselect extends SimpleSAML_Auth_Source {
 	 * @param array $config  Configuration.
 	 */
 	public function __construct($info, $config) {
-		assert('is_array($info)');
-		assert('is_array($config)');
-
 		/* Call the parent constructor first, as required by the interface. */
 		parent::__construct($info, $config);
 
-		if (!array_key_exists('serverurl', $config)) throw new Exception('aselect serverurl not specified');
-		$this->asconfig['serverurl'] = $config['serverurl'];
+		$cfg = SimpleSAML_Configuration::loadFromArray($config,
+			'Authentication source ' . var_export($this->authId, true));
 
-		if (!array_key_exists('serverid', $config)) throw new Exception('aselect serverid not specified');
-		$this->asconfig['serverid'] = $config['serverid'];
+		$cfg->getValueValidate('type', array('app'), 'app');
+		$this->app_id = $cfg->getString('app_id');
+		$this->private_key = $cfg->getString('private_key', null);
 
-		if (!array_key_exists('type', $config)) throw new Exception('aselect type not specified');
-		$this->asconfig['type'] = $config['type'];
+		// accept these arguments with '_' for consistency
+		// accept these arguments without '_' for backwards compatibility
+		$this->server_id = $cfg->getString('serverid', null);
+		if($this->server_id === null)
+			$this->server_id = $cfg->getString('server_id');
 
-		if ($this->asconfig['type'] == 'app') {
-			if (!array_key_exists('app_id', $config)) throw new Exception('aselect app_id not specified');
-			$this->asconfig['app_id'] = $config['app_id'];
-		} elseif($this->asconfig['type'] == 'cross') {
-			if (!array_key_exists('local_organization', $config)) throw new Exception('aselect local_organization not specified');
-			$this->asconfig['local_organization'] = $config['local_organization'];
+		$this->server_url = $cfg->getString('serverurl', null);
+		if($this->server_url === null)
+			$this->server_url = $cfg->getString('server_url');
+	}
 
-			$this->asconfig['required_level'] = (array_key_exists('required_level', $config)) ? $config['required_level'] : 10;
-		} else {
-			throw new Exception('aselect type need to be either app or cross');
-		}
+	/**
+	 * Initiate authentication.
+	 *
+	 * @param array &$state  Information about the current authentication.
+	 */
+	public function authenticate(&$state) {
+		$state['aselect::authid'] = $this->authId;
+		$id = SimpleSAML_Auth_State::saveState($state, 'aselect:login', true);
+
+		try {
+			$app_url = SimpleSAML_Module::getModuleURL('aselect/credentials.php', array('ssp_state' => $id));
+			$as_url = $this->request_authentication($app_url);
 
+			SimpleSAML_Utilities::redirect($as_url);
+		} catch(Exception $e) {
+			// attach the exception to the state
+			SimpleSAML_Auth_State::throwException($state, $e);
+		}
 	}
 
+	/**
+	 * Sign a string using the configured private key
+	 *
+	 * @param string $str  The string to calculate a signature for
+	 */
+	private function base64_signature($str) {
+		$key = openssl_pkey_get_private($this->private_key);
+		if($key === false)
+			throw new SimpleSAML_Error_Exception("Unable to load private key: ".openssl_error_string());
+		if(!openssl_sign($str, $sig, $key))
+			throw new SimpleSAML_Error_Exception("Unable to create signature: ".openssl_error_string());
+		openssl_pkey_free($key);
+		return base64_encode($sig);
+	}
 
-	// helper function for sending a non-browser request to a remote server
-	function as_call($url) {
-		$ch = curl_init();
-		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
-		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-		curl_setopt($ch, CURLOPT_URL, $url);
-		$result = curl_exec($ch);
-		$error = curl_error($ch);
-		curl_close($ch);
-		if ($result == FALSE) {
-			throw new Exception('Request on remote server failed: ' . $error);
-		}
-		$parms = array();
-		foreach (explode('&', $result) as $parm) {
-			$tuple = explode('=', $parm);
-			$parms[urldecode($tuple[0])] = urldecode($tuple[1]);
-		}
-		if ($parms['result_code'] != '0000') {
-			throw new Exception('Request on remote server returned error: ' . $result);
+	/**
+	 * Parse a base64 encoded attribute blob. Can't use parse_str() because it
+	 * may contain multi-valued attributes.
+	 *
+	 * @param string $base64  The base64 string to decode.
+	 */
+	private static function decode_attributes($base64) {
+		$blob = base64_decode($base64, true);
+		if($blob === false)
+			throw new SimpleSAML_Error_Exception("Attributes parameter base64 malformed");
+		$pairs = explode('&', $blob);
+		$ret = array();
+		foreach($pairs as $pair) {
+			$keyval = explode('=', $pair, 2);
+			if(count($keyval) < 2)
+				throw new SimpleSAML_Error_Exception("Missing value in attributes parameter");
+			$key = urldecode($keyval[0]);
+			$val = urldecode($keyval[1]);
+			$ret[$key][] = $val;
 		}
-		return $parms;
+		return $ret;
 	}
 
+	/**
+	 * Default options for curl invocations.
+	 */
+	private static $curl_options = array(
+		CURLOPT_BINARYTRANSFER => true,
+		CURLOPT_FAILONERROR => true,
+		CURLOPT_RETURNTRANSFER => true,
+		CURLOPT_CONNECTTIMEOUT => 1,
+		CURLOPT_TIMEOUT => 5,
+		CURLOPT_USERAGENT => "simpleSAMLphp",
+	);
 
 	/**
-	 * Log-in using A-Select
+	 * Create a (possibly signed) URL to contact the A-Select server.
 	 *
-	 * @param array &$state  Information about the current authentication.
+	 * @param string $request    The name of the request (authenticate / verify_credentials).
+	 * @param array $parameters  The parameters to pass for this request.
 	 */
-	public function authenticate(&$state) {
-		assert('is_array($state)');
-
-		/* We are going to need the authId in order to retrieve this authentication source later. */
-		$state[self::AUTHID] = $this->authId;
-
-		$stateID = SimpleSAML_Auth_State::saveState($state, self::STAGE_INIT);
-
-		$serviceUrl = SimpleSAML_Module::getModuleURL('aselect/linkback.php', array('stateID' => $stateID));
-
-		if ($this->asconfig['type'] == 'app') {
-			$params = array(
-				'request'               => 'authenticate',
-				'a-select-server'       => $this->asconfig['serverid'],
-				'app_id'                => $this->asconfig['app_id'],
-				'app_url'               => $serviceUrl,
-			);
-		} else { // type = cross
-			$params = array(
-				'request'               => 'authenticate',
-				'a-select-server'       => $this->asconfig['serverid'],
-				'local_organization'    => $this->asconfig['local_organization'],
-				'required_level'        => $this->asconfig['required_level'],
-				'local_as_url'          => $serviceUrl,
-
-			);
+	private function create_aselect_url($request, $parameters) {
+		$parameters['request'] = $request;
+		$parameters['a-select-server'] = $this->server_id;
+		if(!is_null($this->private_key)) {
+			$signable = '';
+			foreach(array('a-select-server', 'app_id', 'app_url', 'aselect_credentials', 'rid') as $p)
+				if(array_key_exists($p, $parameters))
+					$signable .= $parameters[$p];
+			$parameters['signature'] = $this->base64_signature($signable);
 		}
-		$url = SimpleSAML_Utilities::addURLparameter($this->asconfig['serverurl'],$params);
+		return SimpleSAML_Utilities::addURLparameter($this->server_url, $parameters);
+	}
+
+	/**
+	 * Contact the A-Select server and return the result as an associative array.
+	 *
+	 * @param string $request    The name of the request (authenticate / verify_credentials).
+	 * @param array $parameters  The parameters to pass for this request.
+	 */
+	private function call_aselect($request, $parameters) {
+		$url = $this->create_aselect_url($request, $parameters);
+
+		$curl = curl_init($url);
+		if($curl === false)
+			throw new SimpleSAML_Error_Exception("Unable to create CURL handle");
+
+		if(!curl_setopt_array($curl, self::$curl_options))
+			throw new SimpleSAML_Error_Exception("Unable to set CURL options: ".curl_error($curl));
+
+		$str = curl_exec($curl);
+		$err = curl_error($curl);
+
+		curl_close($curl);
+
+		if($str === false)
+			throw new SimpleSAML_Error_Exception("Unable to retrieve URL: $error");
 
-		$parm = $this->as_call($url);
+		parse_str($str, $res);
 
-		SimpleSAML_Utilities::redirect(
-			$parm['as_url'],
-			array(
-				'rid'               => $parm['rid'],
-				'a-select-server'   => $this->asconfig['serverid'],
-			)
-		);
+		// message is only available with some A-Select server implementations
+		if($res['result_code'] != '0000')
+			if(array_key_exists('message', $res))
+				throw new SimpleSAML_Error_Exception("Unable to contact SSO service: result_code=".$res['result_code']." message=".$res['message']);
+			else
+				throw new SimpleSAML_Error_Exception("Unable to contact SSO service: result_code=".$res['result_code']);
+		unset($res['result_code']);
+
+		return $res;
 	}
 
-	public function finalStep(&$state) {
-		$credentials = $state['aselect:credentials'];
-		$rid = $state['aselect:rid'];
-		assert('isset($credentials)');
-		assert('isset($rid)');
-
-		$params = array(
-			'request'               => 'verify_credentials',
-			'rid'                   => $rid,
-			'a-select-server'       => $this->asconfig['serverid'],
-			'aselect_credentials'   => $credentials,
-		);
-		if ($this->asconfig['type'] == 'cross') {
-			$params['local_organization'] = $this->asconfig['local_organization'];
-		}
+	/**
+	 * Initiate authentication. Returns a URL to redirect the user to.
+	 *
+	 * @param string $app_url  The SSP URL to return to after authenticating (similar to an ACS).
+	 */
+	public function request_authentication($app_url) {
+		$res = $this->call_aselect('authenticate',
+			array('app_id' => $this->app_id, 'app_url' => $app_url));
 
-		$url = SimpleSAML_Utilities::addURLparameter($this->asconfig['serverurl'], $params);
-
-		$parms = $this->as_call($url);
-		$attributes = array('uid' => array($parms['uid']));
-
-		if (array_key_exists('attributes', $parms)) {
-			$decoded = base64_decode($parms['attributes']);
-			foreach (explode('&', $decoded) as $parm) {
-				$tuple = explode('=', $parm);
-				$name = urldecode($tuple[0]);
-				if (preg_match('/\[\]$/',$name)) {
-					$name = substr($name, 0 ,-2);
-				}
-				if (!array_key_exists($name, $attributes)) {
-					$attributes[$name] = array();
-				}
-				$attributes[$name][] = urldecode($tuple[1]);
-			}
-		}
-		$state['Attributes'] = $attributes;
+		$as_url = $res['as_url'];
+		unset($res['as_url']);
+
+		return SimpleSAML_Utilities::addURLparameter($as_url, $res);
+	}
+
+	/**
+	 * Verify the credentials upon return from the A-Select server. Returns an associative array
+	 * with the information given by the A-Select server. Any attributes are pre-parsed.
+	 *
+	 * @param string $server_id    The A-Select server ID as passed by the client
+	 * @param string $credentials  The credentials as passed by the client
+	 * @param string $rid          The request ID as passed by the client
+	 */
+	public function verify_credentials($server_id, $credentials, $rid) {
+		if($server_id != $this->server_id)
+			throw new SimpleSAML_Error_Exception("Acquired server ID ($server_id) does not match configured server ID ($this->server_id)");
+
+		$res = $this->call_aselect('verify_credentials',
+			array('aselect_credentials' => $credentials, 'rid' => $rid));
+
+		if(array_key_exists('attributes', $res))
+			$res['attributes'] = self::decode_attributes($res['attributes']);
 
-		SimpleSAML_Auth_Source::completeAuth($state);
+		return $res;
 	}
 }
diff --git a/modules/aselect/www/credentials.php b/modules/aselect/www/credentials.php
new file mode 100644
index 0000000000000000000000000000000000000000..3d3b8cba1204a903c768285f831c544bc253a113
--- /dev/null
+++ b/modules/aselect/www/credentials.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Check the credentials that the user got from the A-Select server.
+ * This function is called after the user returns from the A-Select server.
+ *
+ * @author Wessel Dankers, Tilburg University
+ */
+function check_credentials() {
+	$state = SimpleSAML_Auth_State::loadState($_REQUEST['ssp_state'], 'aselect:login');
+
+	if(!array_key_exists('a-select-server', $_REQUEST))
+		SimpleSAML_Auth_State::throwException($state, new SimpleSAML_Error_Exception("Missing a-select-server parameter"));
+	$server_id = $_REQUEST['a-select-server'];
+
+	if(!array_key_exists('aselect_credentials', $_REQUEST))
+		SimpleSAML_Auth_State::throwException($state, new SimpleSAML_Error_Exception("Missing aselect_credentials parameter"));
+	$credentials = $_REQUEST['aselect_credentials'];
+
+	if(!array_key_exists('rid', $_REQUEST))
+		SimpleSAML_Auth_State::throwException($state, new SimpleSAML_Error_Exception("Missing rid parameter"));
+	$rid = $_REQUEST['rid'];
+
+	try {
+		if(!array_key_exists('aselect::authid', $state))
+			throw new SimpleSAML_Error_Exception("ASelect authentication source missing in state");
+		$authid = $state['aselect::authid'];
+		$aselect = SimpleSAML_Auth_Source::getById($authid);
+		if(is_null($aselect))
+			throw new SimpleSAML_Error_Exception("Could not find authentication source with id $authid");
+		$creds = $aselect->verify_credentials($server_id, $credentials, $rid);
+
+		if(array_key_exists('attributes', $creds)) {
+			$state['Attributes'] = $creds['attributes'];
+		} else {
+			$res = $creds['res'];
+			$state['Attributes'] = array('uid' => array($res['uid']), 'organization' => array($res['organization']));
+		}
+	} catch(Exception $e) {
+		SimpleSAML_Auth_State::throwException($state, $e);
+	}
+
+	SimpleSAML_Auth_Source::completeAuth($state);
+	SimpleSAML_Auth_State::throwException($state, new SimpleSAML_Error_Exception("Internal error in A-Select component"));
+}
+
+check_credentials();
diff --git a/modules/aselect/www/linkback.php b/modules/aselect/www/linkback.php
index 1f3efba228777d941f009f22ce31942e48689ea2..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/modules/aselect/www/linkback.php
+++ b/modules/aselect/www/linkback.php
@@ -1,36 +0,0 @@
-<?php
-
-/**
- * Handle linkback() response from A-Select.
- */
-
-
-if (!isset($_GET['stateID'])) {
-	throw new SimpleSAML_Error_BadRequest('Missing stateID parameter.');
-}
-$stateId = (string)$_GET['stateID'];
-
-if (!isset($_GET['aselect_credentials'])) {
-	throw new SimpleSAML_Error_BadRequest('Missing aselect_credentials parameter.');
-}
-if (!isset($_GET['rid'])) {
-	throw new SimpleSAML_Error_BadRequest('Missing ridparameter.');
-}
-
-
-$state = SimpleSAML_Auth_State::loadState($stateId, sspmod_aselect_Auth_Source_aselect::STAGE_INIT);
-$state['aselect:credentials'] = $_GET['aselect_credentials'];
-$state['aselect:rid'] = $_GET['rid'];
-
-
-/* Find authentication source. */
-assert('array_key_exists(sspmod_aselect_Auth_Source_aselect::AUTHID, $state)');
-$sourceId = $state[sspmod_aselect_Auth_Source_aselect::AUTHID];
-
-$source = SimpleSAML_Auth_Source::getById($sourceId);
-if ($source === NULL) {
-	throw new Exception('Could not find authentication source with id ' . $sourceId);
-}
-
-$source->finalStep($state);
-