Skip to content
Snippets Groups Projects
Commit 308a2068 authored by Olav Morken's avatar Olav Morken
Browse files

WS-Fed: Add some more error checking, reuse some simpleSAMLphp code, simplify some code.

Note that this patch has some user-visible changes:
- The entity id of the IdP is must now match the Issuer in the response we get
  from the IdP.
- The 'cert' option in the IdP is replaced with the 'certificate' option. The
  default search path of the certificate is the 'cert' directory.
- The AssertionConsumerService.php handler has been renamed to prp.php.
- The 'realm' option in the sp-hosted metadata is removed. Now we use the
  entity id of the sp instead.


git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@635 44740490-163a-0410-bde0-09ae8108e29a
parent cf9d49a6
No related branches found
No related tags found
No related merge requests found
...@@ -21,8 +21,7 @@ $t->data['remaining'] = $session->remainingTime(); ...@@ -21,8 +21,7 @@ $t->data['remaining'] = $session->remainingTime();
$t->data['sessionsize'] = $session->getSize(); $t->data['sessionsize'] = $session->getSize();
$t->data['attributes'] = $attributes; $t->data['attributes'] = $attributes;
$t->data['icon'] = 'bino.png'; $t->data['icon'] = 'bino.png';
$t->data['logout'] = '[ <a href="/' . $config->getBaseURL() . 'wsfed/sp/initSLO.php?RelayState=/' . $t->data['logout'] = null;
$config->getBaseURL() . 'logout.html">Logout</a> ]';
$t->show(); $t->show();
......
...@@ -28,7 +28,7 @@ if (empty($_GET['RelayState'])) { ...@@ -28,7 +28,7 @@ if (empty($_GET['RelayState'])) {
try { try {
$idpentityid = isset($_GET['idpentityid']) ? $_GET['idpentityid'] : $config->getValue('default-wsfed-idp') ; $idpentityid = isset($_GET['idpentityid']) ? $_GET['idpentityid'] : $config->getString('default-wsfed-idp', NULL);
$spentityid = isset($_GET['spentityid']) ? $_GET['spentityid'] : $metadata->getMetaDataCurrentEntityID('wsfed-sp-hosted'); $spentityid = isset($_GET['spentityid']) ? $_GET['spentityid'] : $metadata->getMetaDataCurrentEntityID('wsfed-sp-hosted');
} catch (Exception $exception) { } catch (Exception $exception) {
...@@ -37,6 +37,9 @@ try { ...@@ -37,6 +37,9 @@ try {
if ($idpentityid == null) { if ($idpentityid == null) {
throw new Exception('IdP discovery for WS-Fed is currently unsupported.');
/* TODO: Add idpdisco.php */
SimpleSAML_Logger::info('WS-Fed - SP.initSSO: No chosen or default IdP, go to WSFeddisco'); SimpleSAML_Logger::info('WS-Fed - SP.initSSO: No chosen or default IdP, go to WSFeddisco');
SimpleSAML_Utilities::redirect('/' . $config->getBaseURL() . 'wsfed/sp/idpdisco.php', array( SimpleSAML_Utilities::redirect('/' . $config->getBaseURL() . 'wsfed/sp/idpdisco.php', array(
...@@ -51,15 +54,16 @@ try { ...@@ -51,15 +54,16 @@ try {
$idpmeta = $metadata->getMetaData($idpentityid, 'wsfed-idp-remote'); $idpmeta = $metadata->getMetaData($idpentityid, 'wsfed-idp-remote');
$spmeta = $metadata->getMetaData($spentityid, 'wsfed-sp-hosted'); $spmeta = $metadata->getMetaData($spentityid, 'wsfed-sp-hosted');
$url = $idpmeta['prp'] .
'?wa=wsignin1.0' .
'&wct=' . gmdate("Y-m-d\TH:i:s\Z", time()) .
'&wtrealm=' . $spmeta['realm'] .
'&wctx=' . urlencode($relaystate);
SimpleSAML_Utilities::redirect($url); SimpleSAML_Utilities::redirect($idpmeta['prp'], array(
'wa' => 'wsignin1.0',
'wct' => gmdate('Y-m-d\TH:i:s\Z', time()),
'wtrealm' => $spentityid,
'wctx' => $relaystate
));
} catch (Exception $exception) { } catch (Exception $exception) {
SimpleSAML_Utilities::fatalError($session->getTrackID(), 'CREATEREQUEST', $exception); SimpleSAML_Utilities::fatalError($session->getTrackID(), 'CREATEREQUEST', $exception);
} }
?>
\ No newline at end of file
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
/** /**
* WS-Federation/ADFS PRP protocol support for simpleSAMLphp. * WS-Federation/ADFS PRP protocol support for simpleSAMLphp.
* *
* This endpoint currently only supports assertions. Logout is not implemented.
*
* The AssertionConsumerService handler accepts responses from a WS-Federation * The AssertionConsumerService handler accepts responses from a WS-Federation
* Account Partner using the Passive Requestor Profile (PRP) and handles it as * Account Partner using the Passive Requestor Profile (PRP) and handles it as
* a Resource Partner. It receives a response, parses it and passes on the * a Resource Partner. It receives a response, parses it and passes on the
...@@ -20,92 +22,119 @@ $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); ...@@ -20,92 +22,119 @@ $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
SimpleSAML_Logger::info('WS-Fed - SP.AssertionConsumerService: Accessing WS-Fed SP endpoint AssertionConsumerService'); SimpleSAML_Logger::info('WS-Fed - SP.AssertionConsumerService: Accessing WS-Fed SP endpoint AssertionConsumerService');
if (!$config->getValue('enable.wsfed-sp', false)) if (!$config->getBoolean('enable.wsfed-sp', false))
SimpleSAML_Utilities::fatalError($session->getTrackID(), 'NOACCESS'); SimpleSAML_Utilities::fatalError($session->getTrackID(), 'NOACCESS');
if (empty($_POST['wresult'])) /* Make sure that the correct query parameters are passed to this script. */
SimpleSAML_Utilities::fatalError($session->getTrackID(), 'ACSPARAMS', $exception); try {
if (empty($_POST['wresult'])) {
// verify the response from the Account Partner, containing the assertion throw new Exception('Missing wresult parameter');
function wsf_verify_response($dom, $cert) {
$objXMLSecDSig = new XMLSecurityDSig();
$objXMLSecDSig->idKeys[] = 'AssertionID';
$signatureElement = $objXMLSecDSig->locateSignature($dom);
if (!$signatureElement) {
throw new Exception('Could not locate XML Signature element.');
} }
$objXMLSecDSig->canonicalizeSignedInfo(); if (empty($_POST['wa'])) {
if (!$objXMLSecDSig->validateReference()) { throw new Exception('Missing wa parameter');
throw new Exception('XMLsec: digest validation failed');
} }
$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'public')); if (empty($_POST['wctx'])) {
$objKey->loadKey($cert, TRUE, TRUE); throw new Exception('Missing wctx parameter');
if (! $objXMLSecDSig->verify($objKey)) {
throw new Exception('Unable to validate Signature');
} }
} catch(Exception $exception) {
SimpleSAML_Utilities::fatalError($session->getTrackID(), 'ACSPARAMS', $exception);
} }
try { try {
$idpmetadata = $metadata->getMetaData($session->getIdP(), 'wsfed-idp-remote');
$spmetadata = $metadata->getMetaDataCurrent();
$wa = $_POST['wa']; $wa = $_POST['wa'];
$wresult = $_POST['wresult']; $wresult = $_POST['wresult'];
$wctx = $_POST['wctx']; $wctx = $_POST['wctx'];
$attributes = array(); /* Load and parse the XML. */
$dom = new DOMDocument(); $dom = new DOMDocument();
# accommodate for MS-ADFS escaped quotes /* Accommodate for MS-ADFS escaped quotes */
$wresult = str_replace('\"', '"', $wresult); $wresult = str_replace('\"', '"', $wresult);
$dom->loadXML(str_replace ("\r", "", $wresult)); $dom->loadXML(str_replace ("\r", "", $wresult));
wsf_verify_response($dom, $config->getBaseDir() . $idpmetadata['cert']);
$session = SimpleSAML_Session::getInstance(true);
$xpath = new DOMXpath($dom); $xpath = new DOMXpath($dom);
$xpath->registerNamespace('wst', 'http://schemas.xmlsoap.org/ws/2005/02/trust'); $xpath->registerNamespace('wst', 'http://schemas.xmlsoap.org/ws/2005/02/trust');
$xpath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion'); $xpath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
$assertions = $xpath->query('/wst:RequestSecurityTokenResponse/wst:RequestedSecurityToken/saml:Assertion', $dom->documentElement);
foreach ($assertions as $assertion) { /* Find the saml:Assertion element in the response. */
$statement = $xpath->query('saml:AuthenticationStatement', $assertion); $assertions = $xpath->query('/wst:RequestSecurityTokenResponse/wst:RequestedSecurityToken/saml:Assertion');
if (($statement == NULL) or ($statement->item(0) == NULL)) { if ($assertions->length === 0) {
throw new Exception('no authentication statement found'); throw new Exception('Received a response without an assertion on the WS-Fed PRP handler.');
} }
// TODO: only process first authentication statement for now; if ($assertions->length > 1) {
$subject = $xpath->query('saml:Subject', $statement->item(0)); throw new Exception('The WS-Fed PRP handler currently only supports a single assertion in a response.');
if (($subject == NULL) or ($subject->item(0) == NULL)) { }
throw new Exception('no subject found in authentication statement'); $assertion = $assertions->item(0);
}
$nameid = $xpath->query('saml:NameIdentifier', $subject->item(0)); /* Find the entity id of the issuer. */
if (($nameid == NULL) or ($nameid->item(0) == NULL)) { $idpEntityId = $assertion->getAttribute('Issuer');
throw new Exception('no nameid found in subject in authentication statement');
} /* Load the IdP metadata. */
$session->setNameID(array( $idpMetadata = $metadata->getMetaData($idpEntityId, 'wsfed-idp-remote');
'Format' => $nameid->item(0)->getAttribute('Format'),
'value' => $nameid->item(0)->textContent, /* Find the certificate used by the IdP. */
) if(array_key_exists('certificate', $idpMetadata)) {
$certFile = $config->getPathvalue('certdir') . $idpMetadata['certificate'];
} elseif(array_key_exists('cert', $idpMetadata)) {
/* For backwards compatibility. */
$certFile = $config->getBaseDir() . $idpMetadata['cert'];
} else {
throw new Exception('Missing \'certificate\' metadata option in the \'wsfed-idp-remote\' metadata' .
' for the IdP \'' . $idpEntityId . '\'.');
}
/* Load the certificate. */
$certData = file_get_contents($certFile);
if($certData === FALSE) {
throw new Exception('Unable to load certificate file \'' . $certFile . '\' for wsfed-idp \'' .
$idpEntityId . '\'.');
}
/* Verify that the assertion is signed by the issuer. */
$validator = new SimpleSAML_XML_Validator($assertion, 'AssertionID', $certData);
if(!$validator->isNodeValidated($assertion)) {
throw new Exception('The assertion was not correctly signed by the WS-Fed IdP \'' .
$idpEntityId . '\'.');
}
/* Extract the name identifier from the response. */
$nameid = $xpath->query('./saml:AuthenticationStatement/saml:Subject/saml:NameIdentifier', $assertion);
if ($nameid->length === 0) {
throw new Exception('Could not find the name identifier in the response from the WS-Fed IdP \'' .
$idpEntityId . '\'.');
}
$nameid = array(
'Format' => $nameid->item(0)->getAttribute('Format'),
'value' => $nameid->item(0)->textContent,
); );
$statement = $xpath->query('saml:AttributeStatement', $assertion);
if (($statement != NULL) and ($statement->item(0) != NULL)) {
foreach ($xpath->query('saml:Attribute/saml:AttributeValue', $statement->item(0)) as $attribute) { /* Extract the attributes from the response. */
$name = $attribute->parentNode->getAttribute('AttributeName'); $attributes = array();
$value = $attribute->textContent; $attributeValues = $xpath->query('./saml:AttributeStatement/saml:Attribute/saml:AttributeValue', $assertion);
if(!array_key_exists($name, $attributes)) { foreach($attributeValues as $attribute) {
$attributes[$name] = array(); $name = $attribute->parentNode->getAttribute('AttributeName');
} $value = $attribute->textContent;
$attributes[$name][] = $value; if(!array_key_exists($name, $attributes)) {
} $attributes[$name] = array();
} }
// TODO: only process first assertion for now; $attributes[$name][] = $value;
break; }
}
$session->setAuthenticated(true, 'wsfed');
/* Mark the user as logged in. */
$session->doLogin('wsfed');
$session->setAttributes($attributes); $session->setAttributes($attributes);
$session->setNameID($nameid);
$session->setIdP($idpEntityId);
/* Redirect the user back to the page which requested the login. */
SimpleSAML_Utilities::redirect($wctx); SimpleSAML_Utilities::redirect($wctx);
} catch(Exception $exception) { } catch(Exception $exception) {
SimpleSAML_Utilities::fatalError($session->getTrackID(), 'PROCESSASSERTION', $exception); SimpleSAML_Utilities::fatalError($session->getTrackID(), 'PROCESSASSERTION', $exception);
} }
?>
\ No newline at end of file
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