diff --git a/lib/SimpleSAML/Bindings/Shib13/Artifact.php b/lib/SimpleSAML/Bindings/Shib13/Artifact.php index 09c6365328ec5e06aedf0e7d1aacd136660f9906..5960dceb988ec10aaf7cd6d4ad2d0891adb30766 100644 --- a/lib/SimpleSAML/Bindings/Shib13/Artifact.php +++ b/lib/SimpleSAML/Bindings/Shib13/Artifact.php @@ -5,174 +5,189 @@ * * @package SimpleSAMLphp */ -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\Utils\Random::generateID() . '"' . - ' MajorVersion="1" MinorVersion="1"' . - ' IssueInstant="' . SimpleSAML\Utils\Time::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)'); - - try { - $doc = \SAML2\DOMDocumentFactory::fromString($soapResponse); - } catch(\Exception $e) { - throw new SimpleSAML_Error_Exception('Error parsing SAML 1 artifact response.'); - } - - $soapEnvelope = $doc->firstChild; - if (!SimpleSAML\Utils\XML::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\Utils\XML::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\Utils\XML::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 = \SAML2\DOMDocumentFactory::create(); - $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); - - \SimpleSAML\Utils\XML::debugSAMLMessage($request, 'out'); - - $url = $idpMetadata->getDefaultEndpoint('ArtifactResolutionService', array('urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding')); - $url = $url['Location']; - - $peerPublicKeys = $idpMetadata->getPublicKeys('signing', TRUE); - $certData = ''; - foreach ($peerPublicKeys as $key) { - if ($key['type'] !== 'X509Certificate') { - continue; - } - $certData .= "-----BEGIN CERTIFICATE-----\n" . - chunk_split($key['X509Certificate'], 64) . - "-----END CERTIFICATE-----\n"; - } - - $file = SimpleSAML\Utils\System::getTempDir() . DIRECTORY_SEPARATOR . sha1($certData) . '.crt'; - if (!file_exists($file)) { - SimpleSAML\Utils\System::writeFile($file, $certData); - } - - $spKeyCertFile = \SimpleSAML\Utils\Config::getCertPath($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', - ), - ); - - // Fetch the artifact - $response = \SimpleSAML\Utils\HTTP::fetch($url, $opts); - if ($response === FALSE) { - throw new SimpleSAML_Error_Exception('Failed to retrieve assertion from IdP.'); - } - - \SimpleSAML\Utils\XML::debugSAMLMessage($response, 'in'); - - // Find the response in the SOAP message - $response = self::extractResponse($response); - - return $response; - } - -} \ No newline at end of file + +namespace SimpleSAML\Bindings\Shib13; + +use SAML2\DOMDocumentFactory; +use SimpleSAML\Utils\Config; +use SimpleSAML\Utils\HTTP; +use SimpleSAML\Utils\Random; +use SimpleSAML\Utils\System; +use SimpleSAML\Utils\Time; +use SimpleSAML\Utils\XML; + +class 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="' . Random::generateID() . '"' . + ' MajorVersion="1" MinorVersion="1"' . + ' IssueInstant="' . Time::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. + * @throws \SimpleSAML_Error_Exception + */ + private static function extractResponse($soapResponse) + { + assert('is_string($soapResponse)'); + + try { + $doc = DOMDocumentFactory::fromString($soapResponse); + } catch (\Exception $e) { + throw new \SimpleSAML_Error_Exception('Error parsing SAML 1 artifact response.'); + } + + $soapEnvelope = $doc->firstChild; + if (!XML::isDOMElementOfType($soapEnvelope, 'Envelope', 'http://schemas.xmlsoap.org/soap/envelope/')) { + throw new \SimpleSAML_Error_Exception('Expected artifact response to contain a <soap:Envelope> element.'); + } + + $soapBody = XML::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 = XML::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 = DOMDocumentFactory::create(); + $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. + * @throws \SimpleSAML_Error_Exception + */ + public static function receive(\SimpleSAML_Configuration $spMetadata, \SimpleSAML_Configuration $idpMetadata) + { + $artifacts = self::getArtifacts(); + $request = self::buildRequest($artifacts); + + XML::debugSAMLMessage($request, 'out'); + + $url = $idpMetadata->getDefaultEndpoint('ArtifactResolutionService', array('urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding')); + $url = $url['Location']; + + $peerPublicKeys = $idpMetadata->getPublicKeys('signing', true); + $certData = ''; + foreach ($peerPublicKeys as $key) { + if ($key['type'] !== 'X509Certificate') { + continue; + } + $certData .= "-----BEGIN CERTIFICATE-----\n" . + chunk_split($key['X509Certificate'], 64) . + "-----END CERTIFICATE-----\n"; + } + + $file = System::getTempDir() . DIRECTORY_SEPARATOR . sha1($certData) . '.crt'; + if (!file_exists($file)) { + System::writeFile($file, $certData); + } + + $spKeyCertFile = Config::getCertPath($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', + ), + ); + + // Fetch the artifact + $response = HTTP::fetch($url, $opts); + if ($response === false) { + throw new \SimpleSAML_Error_Exception('Failed to retrieve assertion from IdP.'); + } + + XML::debugSAMLMessage($response, 'in'); + + // Find the response in the SOAP message + $response = self::extractResponse($response); + + return $response; + } +} diff --git a/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php b/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php index 635fd05dad4edcce5130e91ddbda15c933b4deb5..58824747e30440a9c1303670b9c99e4e10c57e84 100644 --- a/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php +++ b/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php @@ -7,29 +7,39 @@ * @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no> * @package SimpleSAMLphp */ -class SimpleSAML_Bindings_Shib13_HTTPPost + +namespace SimpleSAML\Bindings\Shib13; + +use SAML2\DOMDocumentFactory; +use SimpleSAML\Utils\Crypto; +use SimpleSAML\Utils\HTTP; +use SimpleSAML\Utils\XML; +use SimpleSAML\XML\Shib13\AuthnResponse; +use SimpleSAML\XML\Signer; + +class HTTPPost { /** - * @var SimpleSAML_Configuration + * @var \SimpleSAML_Configuration */ private $configuration = null; /** - * @var SimpleSAML_Metadata_MetaDataStorageHandler + * @var \SimpleSAML_Metadata_MetaDataStorageHandler */ private $metadata = null; /** - * Constructor for the SimpleSAML_Bindings_Shib13_HTTPPost class. + * Constructor for the \SimpleSAML\Bindings\Shib13\HTTPPost class. * - * @param SimpleSAML_Configuration $configuration The configuration to use. - * @param SimpleSAML_Metadata_MetaDataStorageHandler $metadatastore A store where to find metadata. + * @param \SimpleSAML_Configuration $configuration The configuration to use. + * @param \SimpleSAML_Metadata_MetaDataStorageHandler $metadatastore A store where to find metadata. */ public function __construct( - SimpleSAML_Configuration $configuration, - SimpleSAML_Metadata_MetaDataStorageHandler $metadatastore + \SimpleSAML_Configuration $configuration, + \SimpleSAML_Metadata_MetaDataStorageHandler $metadatastore ) { $this->configuration = $configuration; $this->metadata = $metadatastore; @@ -39,26 +49,25 @@ class SimpleSAML_Bindings_Shib13_HTTPPost /** * Send an authenticationResponse using HTTP-POST. * - * @param string $response The response which should be sent. - * @param SimpleSAML_Configuration $idpmd The metadata of the IdP which is sending the response. - * @param SimpleSAML_Configuration $spmd The metadata of the SP which is receiving the response. - * @param string|null $relayState The relaystate for the SP. - * @param string $shire The shire which should receive the response. + * @param string $response The response which should be sent. + * @param \SimpleSAML_Configuration $idpmd The metadata of the IdP which is sending the response. + * @param \SimpleSAML_Configuration $spmd The metadata of the SP which is receiving the response. + * @param string|null $relayState The relaystate for the SP. + * @param string $shire The shire which should receive the response. */ public function sendResponse( $response, - SimpleSAML_Configuration $idpmd, - SimpleSAML_Configuration $spmd, + \SimpleSAML_Configuration $idpmd, + \SimpleSAML_Configuration $spmd, $relayState, $shire ) { + XML::checkSAMLMessage($response, 'saml11'); - \SimpleSAML\Utils\XML::checkSAMLMessage($response, 'saml11'); + $privatekey = Crypto::loadPrivateKey($idpmd, true); + $publickey = Crypto::loadPublicKey($idpmd, true); - $privatekey = SimpleSAML\Utils\Crypto::loadPrivateKey($idpmd, true); - $publickey = SimpleSAML\Utils\Crypto::loadPublicKey($idpmd, true); - - $responsedom = \SAML2\DOMDocumentFactory::fromString(str_replace("\r", "", $response)); + $responsedom = DOMDocumentFactory::fromString(str_replace("\r", "", $response)); $responseroot = $responsedom->getElementsByTagName('Response')->item(0); $firstassertionroot = $responsedom->getElementsByTagName('Assertion')->item(0); @@ -80,7 +89,7 @@ class SimpleSAML_Bindings_Shib13_HTTPPost $signResponse = true; } - $signer = new \SimpleSAML\XML\Signer(array( + $signer = new Signer(array( 'privatekey_array' => $privatekey, 'publickey_array' => $publickey, 'id' => ($signResponse ? 'ResponseID' : 'AssertionID'), @@ -93,7 +102,7 @@ class SimpleSAML_Bindings_Shib13_HTTPPost if ($signResponse) { // sign the response - this must be done after encrypting the assertion // we insert the signature before the saml2p:Status element - $statusElements = SimpleSAML\Utils\XML::getDOMChildren($responseroot, 'Status', '@saml1p'); + $statusElements = XML::getDOMChildren($responseroot, 'Status', '@saml1p'); assert('count($statusElements) === 1'); $signer->sign($responseroot, $responseroot, $statusElements[0]); } else { @@ -103,9 +112,9 @@ class SimpleSAML_Bindings_Shib13_HTTPPost $response = $responsedom->saveXML(); - \SimpleSAML\Utils\XML::debugSAMLMessage($response, 'out'); + XML::debugSAMLMessage($response, 'out'); - \SimpleSAML\Utils\HTTP::submitPOSTData($shire, array( + HTTP::submitPOSTData($shire, array( 'TARGET' => $relayState, 'SAMLResponse' => base64_encode($response), )); @@ -116,26 +125,24 @@ class SimpleSAML_Bindings_Shib13_HTTPPost * Decode a received response. * * @param array $post POST data received. - * * @return \SimpleSAML\XML\Shib13\AuthnResponse The response decoded into an object. - * - * @throws Exception If there is no SAMLResponse parameter. + * @throws \Exception If there is no SAMLResponse parameter. */ public function decodeResponse($post) { assert('is_array($post)'); if (!array_key_exists('SAMLResponse', $post)) { - throw new Exception('Missing required SAMLResponse parameter.'); + throw new \Exception('Missing required SAMLResponse parameter.'); } $rawResponse = $post['SAMLResponse']; $samlResponseXML = base64_decode($rawResponse); - \SimpleSAML\Utils\XML::debugSAMLMessage($samlResponseXML, 'in'); + XML::debugSAMLMessage($samlResponseXML, 'in'); - \SimpleSAML\Utils\XML::checkSAMLMessage($samlResponseXML, 'saml11'); + XML::checkSAMLMessage($samlResponseXML, 'saml11'); - $samlResponse = new \SimpleSAML\XML\Shib13\AuthnResponse(); + $samlResponse = new AuthnResponse(); $samlResponse->setXML($samlResponseXML); if (array_key_exists('TARGET', $post)) { diff --git a/modules/saml/lib/IdP/SAML1.php b/modules/saml/lib/IdP/SAML1.php index 135883eeae2af205db141aea45dc148c213b16e1..f0e40dc9dd44b7a517f89ff2ba05480ed9218c5e 100644 --- a/modules/saml/lib/IdP/SAML1.php +++ b/modules/saml/lib/IdP/SAML1.php @@ -1,4 +1,5 @@ <?php +use SimpleSAML\Bindings\Shib13\HTTPPost; /** * IdP implementation for SAML 1.1 protocol. @@ -50,7 +51,7 @@ class sspmod_saml_IdP_SAML1 { $ar = new \SimpleSAML\XML\Shib13\AuthnResponse(); $authnResponseXML = $ar->generate($idpMetadata, $spMetadata, $shire, $attributes); - $httppost = new SimpleSAML_Bindings_Shib13_HTTPPost($config, $metadata); + $httppost = new HTTPPost($config, $metadata); $httppost->sendResponse($authnResponseXML, $idpMetadata, $spMetadata, $target, $shire); } diff --git a/modules/saml/www/sp/saml1-acs.php b/modules/saml/www/sp/saml1-acs.php index 75e2444b6f14457315e9d59bdd36467446de23f1..de340e8d96c9b14a2f9d9899d9569c18f91be832 100644 --- a/modules/saml/www/sp/saml1-acs.php +++ b/modules/saml/www/sp/saml1-acs.php @@ -1,5 +1,7 @@ <?php +use SimpleSAML\Bindings\Shib13\Artifact; + if (!array_key_exists('SAMLResponse', $_REQUEST) && !array_key_exists('SAMLart', $_REQUEST)) { throw new SimpleSAML_Error_BadRequest('Missing SAMLResponse or SAMLart parameter.'); } @@ -53,7 +55,7 @@ if (array_key_exists('SAMLart', $_REQUEST)) { } $idpMetadata = $source->getIdPMetadata($state['saml:idp']); - $responseXML = SimpleSAML_Bindings_Shib13_Artifact::receive($spMetadata, $idpMetadata); + $responseXML = Artifact::receive($spMetadata, $idpMetadata); $isValidated = TRUE; /* Artifact binding validated with ssl certificate. */ } elseif (array_key_exists('SAMLResponse', $_REQUEST)) { $responseXML = $_REQUEST['SAMLResponse'];