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

saml: Add SAML 1 artifact support.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@1830 44740490-163a-0410-bde0-09ae8108e29a
parent 60608133
No related branches found
No related tags found
No related merge requests found
......@@ -122,7 +122,14 @@ These options overrides the options set in `saml20-sp-hosted`.
Shibboleth 1.3 options
----------------------
There are no options specific for a Shibboleth 1.3 IdP.
`saml1.useartifact`
: Request that the IdP returns the result to the artifact binding.
The default is to use the POST binding, set this option to TRUE to use the artifact binding instead.
: This option can be set for all IdPs connected to a SP by setting it in the entry for the SP in `config/authsources.php`.
: *Note*: This option only works with the `saml:SP` authentication source.
Examples
......
<?php
/**
* Implementation of the Shibboleth 1.3 Artifact binding.
*
* @package simpleSAMLphp
* @version $Id$
*/
class SimpleSAML_Bindings_Shib13_Artifact {
/**
* Parse the query string, and extract the SAMLart parameters.
*
* This function is required because each query contains multiple
* artifact with the same parameter name.
*
* @return array The artifacts.
*/
private static function getArtifacts() {
assert('array_key_exists("QUERY_STRING", $_SERVER)');
/* We need to process the query string manually, to capture all SAMLart parameters. */
$artifacts = array();
$elements = explode('&', $_SERVER['QUERY_STRING']);
foreach ($elements as $element) {
list($name, $value) = explode('=', $element, 2);
$name = urldecode($name);
$value = urldecode($value);
if ($name === 'SAMLart') {
$artifacts[] = $value;
}
}
return $artifacts;
}
/**
* Build the request we will send to the IdP.
*
* @param array $artifacts The artifacts we will request.
* @return string The request, as an XML string.
*/
private static function buildRequest(array $artifacts) {
$msg = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">' .
'<SOAP-ENV:Body>' .
'<samlp:Request xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol"' .
' RequestID="' . SimpleSAML_Utilities::generateID() . '"' .
' MajorVersion="1" MinorVersion="1"' .
' IssueInstant="' . SimpleSAML_Utilities::generateTimestamp() . '"' .
'>';
foreach ($artifacts as $a) {
$msg .= '<samlp:AssertionArtifact>' . htmlspecialchars($a) . '</samlp:AssertionArtifact>';
}
$msg .= '</samlp:Request>' .
'</SOAP-ENV:Body>' .
'</SOAP-ENV:Envelope>';
return $msg;
}
/**
* Extract the response element from the SOAP response.
*
* @param string $soapResponse The SOAP response.
* @return string The <saml1p:Response> element, as a string.
*/
private static function extractResponse($soapResponse) {
assert('is_string($soapResponse)');
$doc = new DOMDocument();
if (!$doc->loadXML($soapResponse)) {
throw new SimpleSAML_Error_Exception('Error parsing SAML 1 artifact response.');
}
$soapEnvelope = $doc->firstChild;
if (!SimpleSAML_Utilities::isDOMElementOfType($soapEnvelope, 'Envelope', 'http://schemas.xmlsoap.org/soap/envelope/')) {
throw new SimpleSAML_Error_Exception('Expected artifact response to contain a <soap:Envelope> element.');
}
$soapBody = SimpleSAML_Utilities::getDOMChildren($soapEnvelope, 'Body', 'http://schemas.xmlsoap.org/soap/envelope/');
if (count($soapBody) === 0) {
throw new SimpleSAML_Error_Exception('Couldn\'t find <soap:Body> in <soap:Envelope>.');
}
$soapBody = $soapBody[0];
$responseElement = SimpleSAML_Utilities::getDOMChildren($soapBody, 'Response', 'urn:oasis:names:tc:SAML:1.0:protocol');
if (count($responseElement) === 0) {
throw new SimpleSAML_Error_Exception('Couldn\'t find <saml1p:Response> in <soap:Body>.');
}
$responseElement = $responseElement[0];
/*
* Save the <saml1p:Response> element. Note that we need to import it
* into a new document, in order to preserve namespace declarations.
*/
$newDoc = new DOMDocument();
$newDoc->appendChild($newDoc->importNode($responseElement, TRUE));
$responseXML = $newDoc->saveXML();
return $responseXML;
}
/**
* This function receives a SAML 1.1 artifact.
*
* @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @return string The <saml1p:Response> element, as an XML string.
*/
public static function receive(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata) {
$artifacts = self::getArtifacts();
$request = self::buildRequest($artifacts);
$url = 'https://skjak.uninett.no:1245/test...';
$url = $idpMetadata->getString('ArtifactResolutionService');
$certData = SimpleSAML_Utilities::loadPublicKey($idpMetadata->toArray(), TRUE);
if (!array_key_exists('PEM', $certData)) {
throw new SimpleSAML_Error_Exception('Missing one of certData or certificate in metadata for '
. var_export($idpMetadata->getString('entityid'), TRUE));
}
$certData = $certData['PEM'];
$file = SimpleSAML_Utilities::getTempDir() . '/' . sha1($certData) . '.crt';
if (!file_exists($file)) {
SimpleSAML_Utilities::writeFile($file, $certData);
}
$globalConfig = SimpleSAML_Configuration::getInstance();
$spKeyCertFile = $globalConfig->getPathValue('certdir', 'cert/') . $spMetadata->getString('privatekey');
$opts = array(
'ssl' => array(
'verify_peer' => TRUE,
'cafile' => $file,
'local_cert' => $spKeyCertFile,
'capture_peer_cert' => TRUE,
'capture_peer_chain' => TRUE,
),
'http' => array(
'method' => 'POST',
'content' => $request,
'header' => 'SOAPAction: http://www.oasis-open.org/committees/security' . "\r\n" .
'Content-Type: text/xml',
),
);
$context = stream_context_create($opts);
/* Fetch the artifact. */
$response = file_get_contents($url, FALSE, $context);
if ($response === FALSE) {
throw new SimpleSAML_Error_Exception('Failed to retrieve assertion from IdP.');
}
/* Find the response in the SOAP message. */
$response = self::extractResponse($response);
return $response;
}
}
\ No newline at end of file
......@@ -15,6 +15,14 @@ class SimpleSAML_XML_Shib13_AuthnResponse {
private $validator = null;
/**
* Whether this response was validated by some external means (e.g. SSL).
*
* @var bool
*/
private $messageValidated = FALSE;
const SHIB_PROTOCOL_NS = 'urn:oasis:names:tc:SAML:1.0:protocol';
const SHIB_ASSERT_NS = 'urn:oasis:names:tc:SAML:1.0:assertion';
......@@ -34,6 +42,18 @@ class SimpleSAML_XML_Shib13_AuthnResponse {
private $relayState = null;
/**
* Set whether this message was validated externally.
*
* @param bool $messageValidated TRUE if the message is already validated, FALSE if not.
*/
public function setMessageValidated($messageValidated) {
assert('is_bool($messageValidated)');
$this->messageValidated = $messageValidated;
}
public function setXML($xml) {
assert('is_string($xml)');
......@@ -55,6 +75,11 @@ class SimpleSAML_XML_Shib13_AuthnResponse {
public function validate() {
assert('$this->dom instanceof DOMDocument');
if ($this->messageValidated) {
/* This message was validated externally. */
return TRUE;
}
/* Validate the signature. */
$this->validator = new SimpleSAML_XML_Validator($this->dom, array('ResponseID', 'AssertionID'));
......@@ -90,6 +115,11 @@ class SimpleSAML_XML_Shib13_AuthnResponse {
*/
private function isNodeValidated($node) {
if ($this->messageValidated) {
/* This message was validated externally. */
return TRUE;
}
if($this->validator === NULL) {
return FALSE;
}
......
......@@ -130,6 +130,14 @@ Options
: *Note*: SAML 2 specific.
`saml1.useartifact`
: Request that the IdP returns the result to the artifact binding.
The default is to use the POST binding, set this option to TRUE to use the artifact binding instead.
: This option can also be set in the `shib13-idp-remote` metadata, in which case the setting in `shib13-idp-remote` takes precedence.
: *Note*: SAML 1 specific.
`redirect.sign`
: Whether authentication requests, logout requests and logout responses sent from this SP should be signed. The default is `FALSE`.
......
......@@ -149,7 +149,18 @@ class sspmod_saml_Auth_Source_SP extends SimpleSAML_Auth_Source {
$id = SimpleSAML_Auth_State::saveState($state, 'saml:sp:ssosent-saml1');
$ar->setRelayState($id);
$url = $ar->createRedirect($idpEntityId, SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $this->authId));
$useArtifact = $idpMetadata->getBoolean('saml1.useartifact', NULL);
if ($useArtifact === NULL) {
$useArtifact = $this->metadata->getBoolean('saml1.useartifact', FALSE);
}
if ($useArtifact) {
$shire = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $this->authId . '/artifact');
} else {
$shire = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $this->authId);
}
$url = $ar->createRedirect($idpEntityId, $shire);
SimpleSAML_Logger::debug('Starting SAML 1 SSO to ' . var_export($idpEntityId, TRUE) .
' from ' . var_export($this->entityId, TRUE) . '.');
......
<?php
if (!array_key_exists('SAMLResponse', $_REQUEST)) {
throw new SimpleSAML_Error_BadRequest('Missing SAMLResponse parameter.');
if (!array_key_exists('SAMLResponse', $_REQUEST) && !array_key_exists('SAMLart', $_REQUEST)) {
throw new SimpleSAML_Error_BadRequest('Missing SAMLResponse or SAMLart parameter.');
}
if (!array_key_exists('TARGET', $_REQUEST)) {
......@@ -26,15 +26,26 @@ if ($state['saml:sp:AuthId'] !== $sourceId) {
throw new SimpleSAML_Error_Exception('The authentication source id in the URL does not match the authentication source which sent the request.');
}
$spMetadata = $source->getMetadata();
$idpEntityId = $state['saml:idp'];
$idpMetadata = $source->getIdPMetadata($idpEntityId);
$responseXML = $_REQUEST['SAMLResponse'];
$responseXML = base64_decode($responseXML);
if (array_key_exists('SAMLart', $_REQUEST)) {
$responseXML = SimpleSAML_Bindings_Shib13_Artifact::receive($spMetadata, $idpMetadata);
$isValidated = TRUE; /* Artifact binding validated with ssl certificate. */
} elseif (array_key_exists('SAMLResponse', $_REQUEST)) {
$responseXML = $_REQUEST['SAMLResponse'];
$responseXML = base64_decode($responseXML);
$isValidated = FALSE; /* Must check signature on response. */
} else {
assert('FALSE');
}
$response = new SimpleSAML_XML_Shib13_AuthnResponse();
$response->setXML($responseXML);
$response->setMessageValidated($isValidated);
$response->validate();
$responseIssuer = $response->getIssuer();
......
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