Skip to content
Snippets Groups Projects
Commit 84292743 authored by Hans Zandbelt's avatar Hans Zandbelt
Browse files

support for signed requests; get in line with latest SVN changes; prepare for metadata refactoring

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@245 44740490-163a-0410-bde0-09ae8108e29a
parent 74ae269f
No related branches found
No related tags found
No related merge requests found
<?php <?php
// Work-in-Progress: /**
// * A-Select protocol support for simpleSAMLphp
// This code can be used: *
// a. to hook up an A-Select Agent or a "local" A-Select server * @author Hans Zandbelt, SURFnet BV. <hans.zandbelt@surfnet.nl>
// (=SP) and bridge to a SAML 2.0 IdP, * @package simpleSAMLphp
// or * @version $Id$
// b. as a bridge from simpleSAMLphp local/bridge "auth" to a *
// "remote" A-Select server. * This code can be used:
// * a. to hook up an A-Select Agent or a "local" A-Select server
// Connect A-Select Agent: * (=SP) and bridge to a SAML 2.0 IdP,
// configure the agent.xml as follows (example): * or
// <aselect-server-id>default.aselect.org</aselect-server-id> * b. as a bridge from simpleSAMLphp local/bridge "auth" to a
// <url>https://localhost/simplesaml/aselect/handler.php</url> * "remote" A-Select server.
// *
// Connect "local" A-Select Server: * Connect A-Select Agent:
// configure simpleSAMLphp as a "remote" aselect server as follows (example): * configure the agent.xml as follows (example):
// <organization id="simplSAMLphp" * <aselect-server-id>default.aselect.org</aselect-server-id>
// server="localhost" * <url>https: *localhost/simplesaml/aselect/handler.php</url>
// friendly_name="simpleSAMLphp (TEST)" *
// resourcegroup="remote_simplsamlphp_resources" /> * Connect "local" A-Select Server:
// * configure simpleSAMLphp as a "remote" aselect server as follows (example):
// <resourcegroup id="remote_simplsamlphp_resources" * <organization id="simplSAMLphp"
// interval="30"> * server="localhost"
// <resource id="simpleSAMLphp1"> * friendly_name="simpleSAMLphp (TEST)"
// <url>https://localhost/simplesaml/aselect/handler.php</url> * resourcegroup="remote_simplsamlphp_resources" />
// </resource> *
// </resourcegroup> * <resourcegroup id="remote_simplsamlphp_resources"
// * interval="30">
// Bridge to "remote" A-Select Server: * <resource id="simpleSAMLphp1">
// configure simpleSAMLphp as a "local" aselect server as follows (example): * <url>https: *localhost/simplesaml/aselect/handler.php</url>
// <organization id="simplSAMLphp" server="localhost"> * </resource>
// <level>1</level> * </resourcegroup>
// <forced_authenticate>false</forced_authenticate> *
// <attribute_policy>policyA</attribute_policy> * Bridge to "remote" A-Select Server:
// </organization> * configure simpleSAMLphp as a "local" aselect server as follows (example):
// * <organization id="simplSAMLphp" server="localhost">
// set the "auth" handler in the idp-hosted metadata as follows: * <level>1</level>
// 'auth' => 'aselect/handler.php?request=bridge', * <forced_authenticate>false</forced_authenticate>
// * <attribute_policy>policyA</attribute_policy>
// TODO: * </organization>
// - extend config possibilities and move it to config/config.php *
// and metadata/aselect-sp-hosted.php * set the "auth" handler in the idp-hosted metadata as follows:
// - add robustness/error-handling/error-reporting * 'auth' => 'aselect/handler.php?request=bridge',
// - generic bridging *
// - check the crypto related stuff (is encrypting rid enough?) * TODO:
// - more checks on parameters (really needed?) * - extend config possibilities and move it to metadata/aselect-*-*.php
* - add robustness/error-handling/error-reporting
* - generic bridging
* - check the crypto related stuff (is encrypting rid enough?)
* - more checks on parameters (really needed?)
*
* - factor out common, app/local server and remote server code
* - use xmlseclibs or plain openssl, not both
* - preload configured keys from their respective files
*/
require_once('../../www/_include.php'); require_once('../../www/_include.php');
require_once('xmlseclibs.php');
require_once('SimpleSAML/Logger.php'); require_once('SimpleSAML/Logger.php');
require_once('SimpleSAML/Configuration.php'); require_once('SimpleSAML/Configuration.php');
require_once('SimpleSAML/Metadata/MetaDataStorageHandler.php'); require_once('SimpleSAML/Metadata/MetaDataStorageHandler.php');
...@@ -58,9 +68,9 @@ $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); ...@@ -58,9 +68,9 @@ $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$as_config = array( $as_config = array(
'server_id' => 'default.aselect.org', 'server_id' => 'default.aselect.org',
'organization_id' => 'simplSAMLphp', 'organization_id' => 'simpleSAMLphp',
'authsp_level' => '10', 'authsp_level' => '10',
'authsp' => 'simplSAMLphp', 'authsp' => 'simpleSAMLphp',
'app_level' => '10', 'app_level' => '10',
'tgt_exp_time' => '1194590521000', 'tgt_exp_time' => '1194590521000',
'metadata' => $metadata->getMetaData('localhost', 'saml20-sp-hosted'), 'metadata' => $metadata->getMetaData('localhost', 'saml20-sp-hosted'),
...@@ -71,18 +81,63 @@ $as_config = array( ...@@ -71,18 +81,63 @@ $as_config = array(
'logout_url' => '/' . $config->getValue('baseurlpath') . 'logout.html', 'logout_url' => '/' . $config->getValue('baseurlpath') . 'logout.html',
'remote_server_id' => 'default.aselect.org', 'remote_server_id' => 'default.aselect.org',
'remote_organization_id' => 'testorg', 'remote_organization_id' => 'testorg',
'remote_server_url' => 'https://localhost/aselectserver/server' 'remote_server_url' => 'https://localhost/aselectserver/server',
'require_signing' => true,
'verify_certificate' => $config->getBaseDir() . '/cert/aselect.crt',
'sign_requests' => true,
'sign_certificate' => $config->getBaseDir() . '/cert/agent.key'
); );
if ($_GET['local_rid']) session_id($_GET['local_rid']); else if ($_GET['rid']) session_id($_GET['rid']); if ($_GET['local_rid']) session_id($_GET['local_rid']); else if ($_GET['rid']) session_id($_GET['rid']);
session_start(); session_start();
function as_verify_signature($parms, $publickey) {
global $as_config, $logger, $config;
$signature = base64_decode($_GET['signature']);
$data = '';
foreach ($parms as $p) {
if (array_key_exists($p, $_GET)) $data .= $_GET[$p];
}
if (!file_exists($publickey)) {
throw new Exception('Could not find public key file [' . $publickey . '] which is needed to verify the signature.');
}
$xmlseckey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'public'));
$xmlseckey->loadKey($publickey,TRUE);
if (openssl_verify ($data, $signature, $xmlseckey->key) != 1) {
$logger->log(LOG_NOTICE, '1', 'aselect', 'handler', 'request', 'access', 'Signature verification failed: '. openssl_error_string());
throw new Exception('Signature verification failed: ' . openssl_error_string());
}
$logger->log(LOG_NOTICE, '1', 'aselect', 'handler', 'request', 'access', 'Signature on request succesfully verified.');
}
// handle authenticate request from an agent or a local server // handle authenticate request from an agent or a local server
function as_request_authenticate() { function as_request_authenticate() {
global $as_config; global $as_config, $logger;
$logger->log(LOG_NOTICE, '1', 'aselect', 'handler', 'request', 'access', 'local_as_url: ' . $_GET['local_as_url']);
//required_level=1
//signature=mEAywVj%2BMsiK6gcnw6QGKuDWpu5rnDYfgk8yYHzmvSReIhs%2F3H79kAY3ZA53Bm03Npmh6TYPQWRduQrvFvvTrGTQbpw3L0qdGz4X8sfM2ax2UPzhfMU3KtRLN13TPzDakpsPuXtT36t1zuKUsTO127ZRG2itCMGm3WDV%2Futk%2FIA%3D
//forced_logon=false
//local_as_url=https%3A%2F%2Flocalhost%2Faselectserver%2Fserver%3Flocal_rid%3D86E16F83BEFA1AA0
//request=authenticate
//local_organization=testorg
//a-select-server=localhost
// harmonize signature verification between app and local server requests:
// app: a-select-server, app_id, app_url, country, forced_logon, language, remote_organization, uid
// server: a-select-server, country, forced_logon, language, local_as_url, local_organization, required_level, uid
if ($as_config['require_signing']) {
as_verify_signature(array(
'a-select-server','app_id', 'app_url', 'country',
'forced_logon', 'language', 'local_as_url', 'remote_organization',
'local_organization', 'required_level', 'uid'
),
$as_config['verify_certificate']
);
}
$_SESSION['return_url'] = array_key_exists('app_url', $_GET) ? $_GET['app_url'] : $_GET['local_as_url']; $_SESSION['return_url'] = array_key_exists('app_url', $_GET) ? $_GET['app_url'] : $_GET['local_as_url'];
$_SESSION['app_id'] = $_GET['app_id']; $_SESSION['app_id'] = $_GET['app_id'];
print 'result_code=0000' . print 'result_code=0000' .
'&a-select-server=' . $as_config['server_id'] . '&a-select-server=' . $as_config['server_id'] .
'&rid=' . session_id() . '&rid=' . session_id() .
...@@ -111,7 +166,7 @@ function as_request_return() { ...@@ -111,7 +166,7 @@ function as_request_return() {
//TODO; why does xmlseclibs not work!? //TODO; why does xmlseclibs not work!?
//$credentials = $xmlseckey->encryptData($rid); //$credentials = $xmlseckey->encryptData($rid);
if (openssl_public_encrypt($rid, $credentials, $xmlseckey->key) == FALSE) { if (openssl_public_encrypt($rid, $credentials, $xmlseckey->key) == FALSE) {
$logger->log(LOG_INFO, '1', 'aselect', 'handler', 'request', 'access', 'Could not encrypt aselect_credentials: ' . $rid . ' : ' . openssl_error_string()); $logger->log(LOG_NOTICE, '1', 'aselect', 'handler', 'request', 'access', 'Could not encrypt aselect_credentials: ' . $rid . ' : ' . openssl_error_string());
throw new Exception("Could not encrypt credentials!"); throw new Exception("Could not encrypt credentials!");
} }
$redirect = $_SESSION['return_url']; $redirect = $_SESSION['return_url'];
...@@ -130,6 +185,16 @@ function as_request_return() { ...@@ -130,6 +185,16 @@ function as_request_return() {
// handle verify credentials request from agent or local server // handle verify credentials request from agent or local server
function as_request_verify_credentials() { function as_request_verify_credentials() {
global $as_config, $config, $logger; global $as_config, $config, $logger;
// harmonize signature verification between app and local server requests:
// app: a-select-server, aselect_credentials, rid
// server: a-select-server, aselect_credentials, local_organization, rid
if ($as_config['require_signing']) {
as_verify_signature(array(
'a-select-server','aselect_credentials', 'local_organization', 'rid'
),
$as_config['verify_certificate']
);
}
// NB: accomodate for weird a-select behaviour: agent and local a-select server pass in credentials in different ways... // NB: accomodate for weird a-select behaviour: agent and local a-select server pass in credentials in different ways...
$credentials = array_key_exists('local_organization', $_GET) ? base64_decode(urldecode($_GET['aselect_credentials'])) : base64_decode($_GET['aselect_credentials']); $credentials = array_key_exists('local_organization', $_GET) ? base64_decode(urldecode($_GET['aselect_credentials'])) : base64_decode($_GET['aselect_credentials']);
$privatekey = $config->getBaseDir() . '/cert/' . $as_config['metadata']['privatekey']; $privatekey = $config->getBaseDir() . '/cert/' . $as_config['metadata']['privatekey'];
...@@ -141,11 +206,11 @@ function as_request_verify_credentials() { ...@@ -141,11 +206,11 @@ function as_request_verify_credentials() {
//TODO; why does xmlseclibs not work!? //TODO; why does xmlseclibs not work!?
//$decrypted = $xmlseckey->decryptData($credentials); //$decrypted = $xmlseckey->decryptData($credentials);
if (openssl_private_decrypt($credentials, $decrypted, $xmlseckey->key) == FALSE) { if (openssl_private_decrypt($credentials, $decrypted, $xmlseckey->key) == FALSE) {
$logger->log(LOG_INFO, '1', 'aselect', 'handler', 'request', 'access', 'Could not decrypt aselect_credentials: ' . $credentials . ' : ' . openssl_error_string()); $logger->log(LOG_NOTICE, '1', 'aselect', 'handler', 'request', 'access', 'Could not decrypt aselect_credentials: ' . $credentials . ' : ' . openssl_error_string());
throw new Exception("Could not decrypt credentials!"); throw new Exception("Could not decrypt credentials!");
} }
if ($decrypted != session_id()) { if ($decrypted != session_id()) {
$logger->log(LOG_INFO, '1', 'aselect', 'handler', 'request', 'access', 'Credentials incorrect or tampered with: ' . $decrypted . ' != ' . session_id()); $logger->log(LOG_NOTICE, '1', 'aselect', 'handler', 'request', 'access', 'Credentials incorrect or tampered with: ' . $decrypted . ' != ' . session_id());
throw new Exception("Incorrect credentials!"); throw new Exception("Incorrect credentials!");
} }
$session = SimpleSAML_Session::getInstance(); $session = SimpleSAML_Session::getInstance();
...@@ -156,9 +221,10 @@ function as_request_verify_credentials() { ...@@ -156,9 +221,10 @@ function as_request_verify_credentials() {
$serialized .= urlencode($name) . '=' . urlencode($value); $serialized .= urlencode($name) . '=' . urlencode($value);
} }
} }
$nameid = $session->getNameID();
print 'result_code=0000' . print 'result_code=0000' .
'&app_id=' . $_SESSION['app_id'] . '&app_id=' . $_SESSION['app_id'] .
'&uid=' . $session->getNameID() . '&uid=' . $nameid['value'] .
'&organization=' . $as_config['organization_id'] . '&organization=' . $as_config['organization_id'] .
'&authsp_level=' . $as_config['authsp_level'] . '&authsp_level=' . $as_config['authsp_level'] .
'&authsp=' . $as_config['authsp'] . '&authsp=' . $as_config['authsp'] .
...@@ -185,11 +251,12 @@ function as_call($url) { ...@@ -185,11 +251,12 @@ function as_call($url) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_URL, $url);
$logger->log(LOG_NOTICE, '1', 'aselect', 'handler', 'request', 'access', 'Outgoing request: ' . $url);
$result = curl_exec($ch); $result = curl_exec($ch);
$error = curl_error($ch); $error = curl_error($ch);
curl_close($ch); curl_close($ch);
if ($result == FALSE) { if ($result == FALSE) {
$logger->log(LOG_INFO, '1', 'aselect', 'handler', 'request', 'access', 'Request on remote server failed [' . $url . '] : ' . $error); $logger->log(LOG_NOTICE, '1', 'aselect', 'handler', 'request', 'access', 'Request on remote server failed [' . $url . '] : ' . $error);
throw new Exception('Request on remote server failed: ' . $error); throw new Exception('Request on remote server failed: ' . $error);
} }
$parms = array(); $parms = array();
...@@ -198,25 +265,53 @@ function as_call($url) { ...@@ -198,25 +265,53 @@ function as_call($url) {
$parms[$a[0]] = urldecode($a[1]); $parms[$a[0]] = urldecode($a[1]);
} }
if ($parms['result_code'] != '0000') { if ($parms['result_code'] != '0000') {
$logger->log(LOG_INFO, '1', 'aselect', 'handler', 'request', 'access', 'Request on remote server returned error: ' . $result); $logger->log(LOG_NOTICE, '1', 'aselect', 'handler', 'request', 'access', 'Request on remote server returned error: ' . $result);
throw new Exception('Request on remote server returned error: ' . $result); throw new Exception('Request on remote server returned error: ' . $result);
} }
return $parms; return $parms;
} }
// calculate signature on request parameters
function as_compute_signature($parms, $privatekey) {
if (!file_exists($privatekey)) {
throw new Exception('Could not find private key file [' . $privatekey . '] which is needed to sign the request.');
}
$xmlseckey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'private'));
$xmlseckey->loadKey($privatekey,TRUE);
$data = '';
foreach ($parms as $p) $data .= $p;
$signature = '';
if (openssl_sign($data, $signature, $xmlseckey->key) != TRUE) {
$logger->log(LOG_NOTICE, '1', 'aselect', 'handler', 'request', 'access', 'Signing request failed: '. openssl_error_string());
throw new Exception('Signing request failed: ' . openssl_error_string());
}
return urlencode(base64_encode($signature));
}
// handle bridged authentication request from simpleSAMLphp to remote server // handle bridged authentication request from simpleSAMLphp to remote server
function as_request_bridge() { function as_request_bridge() {
global $as_config, $logger; global $as_config, $logger;
// perform authenticate // perform authenticate
$_SESSION['relaystate'] = $_GET['RelayState']; $_SESSION['relaystate'] = $_GET['RelayState'];
$local_as_url = $_SERVER['PHP_SELF'] .
'?request=bridge_return' .
'&local_rid=' . session_id();
$url = $as_config['remote_server_url'] . $url = $as_config['remote_server_url'] .
'?request=authenticate' . '?request=authenticate' .
'&required_level=' . $as_config['app_level'] . '&required_level=' . $as_config['app_level'] .
'&local_organization=' . $as_config['organization_id'] . '&local_organization=' . $as_config['organization_id'] .
'&a-select-server=' . $as_config['remote_server_id'] . '&a-select-server=' . $as_config['remote_server_id'] .
'&local_as_url=' . urlencode($_SERVER['PHP_SELF'] . '&local_as_url=' . urlencode($local_as_url);
'?request=bridge_return' . if ($as_config['sign_requests']) {
'&local_rid=' . session_id()); $url .= '&signature=' . as_compute_signature(array(
$as_config['remote_server_id'],
$local_as_url,
$as_config['organization_id'],
$as_config['app_level']
),
$as_config['sign_certificate']
);
}
$parms = as_call($url); $parms = as_call($url);
header('Location: ' . header('Location: ' .
$parms['as_url'] . $parms['as_url'] .
...@@ -231,7 +326,7 @@ function as_request_bridge_return() { ...@@ -231,7 +326,7 @@ function as_request_bridge_return() {
|| ||
(array_key_exists('rid', $_GET) == FALSE)) (array_key_exists('rid', $_GET) == FALSE))
{ {
$logger->log(LOG_INFO, '1', 'aselect', 'handler', 'request', 'access', 'Error on return from login at remote server!'); $logger->log(LOG_NOTICE, '1', 'aselect', 'handler', 'request', 'access', 'Error on return from login at remote server!');
throw new Exception('Error on return from login at remote server!'); throw new Exception('Error on return from login at remote server!');
} }
$url = $as_config['remote_server_url'] . $url = $as_config['remote_server_url'] .
...@@ -240,10 +335,20 @@ function as_request_bridge_return() { ...@@ -240,10 +335,20 @@ function as_request_bridge_return() {
'&local_organization=' . $as_config['organization_id'] . '&local_organization=' . $as_config['organization_id'] .
'&a-select-server=' . $as_config['remote_server_id'] . '&a-select-server=' . $as_config['remote_server_id'] .
'&aselect_credentials=' . $_GET['aselect_credentials']; '&aselect_credentials=' . $_GET['aselect_credentials'];
if ($as_config['sign_requests']) {
$url .= '&signature=' . as_compute_signature(array(
$as_config['remote_server_id'],
$_GET['aselect_credentials'],
$as_config['organization_id'],
$_GET['rid']
),
$as_config['sign_certificate']
);
}
$parms = as_call($url); $parms = as_call($url);
SimpleSAML_Session::init('aselect', $_GET['rid'], true); $session = SimpleSAML_Session::getInstance(true);
$session = SimpleSAML_Session::getInstance(); $session->setAuthenticated(true, 'aselect');
if (array_key_exists('attributes', $parms)) { if (array_key_exists('attributes', $parms)) {
$parm = base64_decode($parms['attributes']); $parm = base64_decode($parms['attributes']);
...@@ -263,7 +368,7 @@ function as_request_bridge_return() { ...@@ -263,7 +368,7 @@ function as_request_bridge_return() {
// demultiplex incoming request // demultiplex incoming request
try { try {
$logger->log(LOG_INFO, '1', 'aselect', 'handler', 'request', 'access', $_SERVER['REQUEST_URI']); $logger->log(LOG_NOTICE, '1', 'aselect', 'handler', 'request', 'access', $_SERVER['REQUEST_URI']);
if ($_GET['request']) { if ($_GET['request']) {
$handler = 'as_request_' . $_GET['request']; $handler = 'as_request_' . $_GET['request'];
$handler(); $handler();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment