protected static function retrieveMetadata($mdspec) { $type = 'public'; $format = 'xml'; if (is_array($mdspec)) { $type = 'private'; $format = 'array'; } elseif (preg_match("/^(private|public|remote):(xml|php|json|array):(.*)/", $mdspec, $dollar)) { list($dummy, $type, $format, $source) = $dollar; } print "Importing {$mdspec}\n"; switch ($format) { case 'xml': $res = Corto_XmlToArray::xml2array(file_get_contents($source), true); break; case 'php': // the included file must return an array $res = (include $source); break; case 'json': $res = json_decode(file_get_contents($source), 1); break; case 'array': $res = $mdspec; break; } return array($type, $res); }
protected static function _xml2array(&$elements, $level = 1, $namespaceMapping = array()) { static $defaultNs; if ($level == 1) { self::$i = 0; $defaultNs = ''; } $newElement = array(); while (isset($elements[self::$i]) && ($value = $elements[self::$i++])) { if ($value['type'] == 'close') { return $newElement; } elseif ($value['type'] == 'cdata') { continue; } $hashedAttributes = array(); $tagName = $value['tag']; if (isset($value['attributes']) && ($attributes = $value['attributes'])) { foreach ($attributes as $attributeKey => $attributeValue) { unset($attributes[$attributeKey]); if (preg_match("/^xmlns:(.+)\$/", $attributeKey, $namespacePrefixAndTag)) { if (empty(self::$_namespaces[$attributeValue])) { self::$_namespaces[$attributeValue] = $namespacePrefixAndTag[1]; } $namespaceMapping[$namespacePrefixAndTag[1]] = self::$_namespaces[$attributeValue]; $hashedAttributes['_xmlns:' . self::$_namespaces[$attributeValue]] = $attributeValue; } elseif (preg_match("/^xmlns\$/", $attributeKey)) { $defaultNs = self::$_namespaces[$attributeValue]; $hashedAttributes['_xmlns:' . $defaultNs] = $attributeValue; } else { $hashedAttributes[self::ATTRIBUTE_KEY_PREFIX . $attributeKey] = $attributeValue; } } } $complete = array(); if (preg_match("/^(.+):(.+)\$/", $tagName, $namespacePrefixAndTag)) { if (!isset($namespaceMapping[$namespacePrefixAndTag[1]])) { throw new Exception('No namespace defined for: ' . $tagName . ' currently defined are: ' . print_r($namespaceMapping, 1)); } $tagName = $namespaceMapping[$namespacePrefixAndTag[1]] . ":" . $namespacePrefixAndTag[2]; } else { $tagName = $defaultNs . ":" . $tagName; } $complete[self::TAG_NAME_KEY] = $tagName; if ($hashedAttributes) { $complete = array_merge($complete, $hashedAttributes); } if (isset($value['value'])) { $complete[self::VALUE_KEY] = $attributeValue = trim($value['value']); } if ($value['type'] == 'open') { $cs = self::_xml2array($elements, $level + 1, $namespaceMapping); foreach ($cs as $c) { $tagName = $c[self::TAG_NAME_KEY]; unset($c[self::TAG_NAME_KEY]); if (!isset(self::$_singulars[$tagName])) { $complete[$tagName][] = $c; } else { $complete[$tagName] = $c; unset($complete[$tagName][self::TAG_NAME_KEY]); } } } #elseif ($value['type'] == 'complete') { # } $newElement[] = $complete; } return $newElement; }
/** * Sends metadata for the current entity; * Currently ignores the binding, as it its always URI * @return void */ public function metadataservice() { header('Content-type: application/samlmetadata+xml'); $entity = $this->_server->getCurrentMD('entityID'); $md = $this->_server->getPublicMetadata($entity); print Corto_XmlToArray::array2xml($md, 'md:EntityDescriptor'); }
protected static function _xml2array(&$elements, $level = 1, $namespaceMapping = array()) { static $defaultNs; $newElement = array(); while (isset($elements[self::$counter])) { $value = $elements[self::$counter]; self::$counter++; if ($value['type'] == 'close') { return $newElement; } elseif ($value['type'] == 'cdata') { continue; } $hashedAttributes = array(); $tagName = $value['tag']; if (isset($value['attributes']) && ($attributes = $value['attributes'])) { foreach ($attributes as $attributeKey => $attributeValue) { unset($attributes[$attributeKey]); if (preg_match("/^xmlns:(.+)\$/", $attributeKey, $namespacePrefixAndTag)) { if (!self::$_namespaces[$attributeValue]) { self::$_namespaces[$attributeValue] = $namespacePrefixAndTag[1]; } $namespaceMapping[$namespacePrefixAndTag[1]] = self::$_namespaces[$attributeValue]; $hashedAttributes['_xmlns:' . self::$_namespaces[$attributeValue]] = $attributeValue; } elseif (preg_match("/^xmlns\$/", $attributeKey)) { $defaultNs = self::$_namespaces[$attributeValue]; } else { $hashedAttributes[self::ATTRIBUTE_PFX . $attributeKey] = $attributeValue; } } } $complete = array(); if (preg_match("/^(.+):(.+)\$/", $tagName, $namespacePrefixAndTag) && ($prefix = $namespaceMapping[$namespacePrefixAndTag[1]])) { $tagName = $prefix . ":" . $namespacePrefixAndTag[2]; } else { $tagName = $defaultNs . ":" . $tagName; } $complete[self::TAG_NAME_PFX] = $tagName; if ($hashedAttributes) { $complete = array_merge($complete, $hashedAttributes); } if (isset($value['value']) && ($attributeValue = trim($value['value']))) { $complete[self::VALUE_PFX] = $attributeValue; } if ($value['type'] == 'open') { $cs = self::_xml2array($elements, $level + 1, $namespaceMapping); foreach ($cs as $c) { $tagName = $c[self::TAG_NAME_PFX]; unset($c[self::TAG_NAME_PFX]); if (!isset(self::$_singulars[$tagName])) { $complete[$tagName][] = $c; } else { $complete[$tagName] = $c; unset($complete[$tagName][self::TAG_NAME_PFX]); } } } $newElement[] = $complete; } self::$counter = 0; return $newElement; }
public function createNewResponse($request, $attributes = array()) { $response = $this->_createBaseResponse($request); $soon = $this->timeStamp($this->getCurrentMD('NotOnOrAfter', null, null, 300)); $sessionEnd = $this->timeStamp($this->getCurrentMD('SessionEnd', null, null, 60 * 60 * 12)); $response['saml:Assertion'] = array('_xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', '_xmlns:xs' => 'http://www.w3.org/2001/XMLSchema', '_xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol', '_xmlns:saml' => 'urn:oasis:names:tc:SAML:2.0:assertion', '_ID' => $this->getNewId(), '_Version' => '2.0', '_IssueInstant' => $response['_IssueInstant'], 'saml:Issuer' => array('__v' => $response['saml:Issuer']['__v']), 'saml:Subject' => array('saml:NameID' => array('_SPNameQualifier' => $this->getCurrentMD('entityID'), '_Format' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient', '__v' => $this->getNewId()), 'saml:SubjectConfirmation' => array('_Method' => 'urn:oasis:names:tc:SAML:2.0:cm:bearer', 'saml:SubjectConfirmationData' => array('_NotOnOrAfter' => $soon, '_Recipient' => $response['_Destination'], '_InResponseTo' => $request['_ID']))), 'saml:Conditions' => array('_NotBefore' => $response['_IssueInstant'], '_NotOnOrAfter' => $soon, 'saml:AudienceRestriction' => array('saml:Audience' => array('__v' => $request['saml:Issuer']['__v']))), 'saml:AuthnStatement' => array('_AuthnInstant' => $response['_IssueInstant'], '_SessionNotOnOrAfter' => $sessionEnd, 'saml:SubjectLocality' => array('_Address' => $_SERVER['REMOTE_ADDR'], '_DNSName' => $_SERVER['REMOTE_HOST']), 'saml:AuthnContext' => array('saml:AuthnContextClassRef' => array('__v' => 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password')))); if (!isset($attributes['binding'])) { $attributes['binding'] = array(); } $attributes['binding'][] = $response['__']['ProtocolBinding']; foreach ((array) $attributes as $key => $vs) { foreach ($vs as $v) { $attributeStatement[$key][] = $v; } } $attributeConsumingServiceIndex = $request['_AttributeConsumingServiceIndex']; if ($attributeConsumingServiceIndex) { $attributeStatement['AttributeConsumingServiceIndex'] = "AttributeConsumingServiceIndex: {$attributeConsumingServiceIndex}"; } else { $attributeStatement['AttributeConsumingServiceIndex'] = '-no AttributeConsumingServiceIndex given-'; } $response['saml:Assertion']['saml:AttributeStatement'][0]['saml:Attribute'] = Corto_XmlToArray::array2attributes($attributeStatement); return $response; }
<?php /** * Created by PhpStorm. * User: freek * Date: Nov 24, 2010 * Time: 5:05:45 PM * To change this template use File | Settings | File Templates. */ include "../library/Corto/XmlToArray.php"; if ($xml = $_POST['msg']) { $xml = stripslashes($xml); $element = Corto_XmlToArray::xml2array($xml); print "<pre>"; print_r($element); $signatureValue = base64_decode($element['ds:Signature']['ds:SignatureValue']['__v']); $digestValue = $element['ds:Signature']['ds:SignedInfo']['ds:Reference'][0]['ds:DigestValue']['__v']; $certificate = $element['ds:Signature']['ds:KeyInfo']['ds:X509Data']['ds:X509Certificate']['__v']; $publicKey = "-----BEGIN CERTIFICATE-----\n" . chunk_split($certificate, 64) . "-----END CERTIFICATE-----"; #print_r($publicKey); $document = DOMDocument::loadXML($xml); $xp = new DomXPath($document); $xp->registerNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#'); $id = $element['_ID']; $signedElement = $xp->query("//*[@ID = '{$id}']")->item(0); $signature = $xp->query(".//ds:Signature", $signedElement)->item(0); $signedInfo = $xp->query(".//ds:SignedInfo", $signature)->item(0)->C14N(true, false); $signature->parentNode->removeChild($signature); $canonicalXml = $signedElement->C14N(true, false); print_r(htmlspecialchars($xml)); print "\n";
/** * Sign an element using $key * @param $key * @param $element * @return */ protected function _sign($privatekey, $certificate, $element) { $signature = array('__t' => 'ds:Signature', '_xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'ds:SignedInfo' => array('__t' => 'ds:SignedInfo', '_xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'ds:CanonicalizationMethod' => array('_Algorithm' => 'http://www.w3.org/2001/10/xml-exc-c14n#'), 'ds:SignatureMethod' => array('_Algorithm' => 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'), 'ds:Reference' => array('_URI' => '__placeholder__', 'ds:Transforms' => array('ds:Transform' => array(array('_Algorithm' => 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'), array('_Algorithm' => 'http://www.w3.org/2001/10/xml-exc-c14n#'))), 'ds:DigestMethod' => array('_Algorithm' => 'http://www.w3.org/2000/09/xmldsig#sha1'), 'ds:DigestValue' => array('__v' => '__placeholder__')))); $canonicalXml = DOMDocument::loadXML(Corto_XmlToArray::array2xml($element))->firstChild->C14N(true, false); $signature['ds:SignedInfo']['ds:Reference']['ds:DigestValue']['__v'] = base64_encode(sha1($canonicalXml, TRUE)); $signature['ds:SignedInfo']['ds:Reference']['_URI'] = "#" . $element['_ID']; $canonicalXml2 = DOMDocument::loadXML(Corto_XmlToArray::array2xml($signature['ds:SignedInfo']))->firstChild->C14N(true, false); $privatekey = file_get_contents(dirname(__FILE__) . '/../../../privatekeys/' . $privatekey); $key = openssl_pkey_get_private($privatekey); openssl_sign($canonicalXml2, $signatureValue, $key); openssl_free_key($key); $signature['ds:SignatureValue']['__v'] = base64_encode($signatureValue); $signature['ds:KeyInfo']['ds:X509Data']['ds:X509Certificate']['__v'] = $certificate; $element['ds:Signature'] = $signature; foreach ($element as $tag => $item) { if ($tag == 'ds:Signature') { continue; } $newelement[$tag] = $item; if ($tag == 'saml:Issuer') { $newelement['ds:Signature'] = $signature; } } #print_r($newelement); exit; return $newelement; }
/** * Ask the user for consent over all of the attributes being sent to the SP. * * Note this is part 1/2 of the Corto Consent Internal Response Processing service. * * @return void */ public function provideConsentService() { $response = $this->_server->getBindingsModule()->receiveResponse(); $_SESSION['consent'][$response['_ID']]['response'] = $response; $attributes = Corto_XmlToArray::attributes2array($response['saml:Assertion']['saml:AttributeStatement'][0]['saml:Attribute']); $serviceProviderEntityId = $attributes['ServiceProvider'][0]; unset($attributes['ServiceProvider']); $spEntityMetadata = $this->_server->getRemoteEntity($serviceProviderEntityId); $identityProviderEntityId = $response['__']['OriginalIssuer']; $idpEntityMetadata = $this->_server->getRemoteEntity($identityProviderEntityId); $commonName = $attributes['urn:mace:dir:attribute-def:cn'][0]; // Apply ARP $arpFilter = new EngineBlock_Corto_Filter_Command_AttributeReleasePolicy(); $arpFilter->setIdpMetadata($idpEntityMetadata); $arpFilter->setSpMetadata($spEntityMetadata); $arpFilter->setResponseAttributes($attributes); $arpFilter->execute(); $attributes = $arpFilter->getResponseAttributes(); $priorConsent = $this->_hasStoredConsent($serviceProviderEntityId, $response, $attributes); if ($priorConsent) { $response['_Consent'] = 'urn:oasis:names:tc:SAML:2.0:consent:prior'; $response['_Destination'] = $response['__']['Return']; $response['__']['ProtocolBinding'] = 'INTERNAL'; $this->_server->getBindingsModule()->send($response, $spEntityMetadata); return; } if (isset($spEntityMetadata['NoConsentRequired']) && $spEntityMetadata['NoConsentRequired']) { $response['_Consent'] = 'urn:oasis:names:tc:SAML:2.0:consent:inapplicable'; $response['_Destination'] = $response['__']['Return']; $response['__']['ProtocolBinding'] = 'INTERNAL'; $this->_server->getBindingsModule()->send($response, $spEntityMetadata); return; } $html = $this->_server->renderTemplate('consent', array('action' => $this->_server->getCurrentEntityUrl('processConsentService'), 'ID' => $response['_ID'], 'attributes' => $attributes, 'sp' => $spEntityMetadata, 'idp' => $idpEntityMetadata, 'commonName' => $commonName)); $this->_server->sendOutput($html); }
protected function _sign($key, $element) { $signature = array('__t' => 'ds:Signature', '_xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'ds:SignedInfo' => array('__t' => 'ds:SignedInfo', '_xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'ds:CanonicalizationMethod' => array('_Algorithm' => 'http://www.w3.org/2001/10/xml-exc-c14n#'), 'ds:SignatureMethod' => array('_Algorithm' => 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'), 'ds:Reference' => array(0 => array('_URI' => '__placeholder__', 'ds:Transforms' => array('ds:Transform' => array(array('_Algorithm' => 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'), array('_Algorithm' => 'http://www.w3.org/2001/10/xml-exc-c14n#')))), 'ds:DigestMethod' => array('_Algorithm' => 'http://www.w3.org/2000/09/xmldsig#sha1'), 'ds:DigestValue' => array('__v' => '__placeholder__'))), 'ds:SignatureValue' => array('__v' => '__placeholder__')); $canonicalXml = DOMDocument::loadXML(Corto_XmlToArray::array2xml($element))->firstChild->C14N(true, false); $signature['ds:SignedInfo']['ds:Reference'][0]['ds:DigestValue']['__v'] = base64_encode(sha1($canonicalXml, TRUE)); $signature['ds:SignedInfo']['ds:Reference'][0]['_URI'] = "#" . $element['_ID']; $canonicalXml2 = DOMDocument::loadXML(Corto_XmlToArray::array2xml($signature['ds:SignedInfo']))->firstChild->C14N(true, false); openssl_sign($canonicalXml2, $signatureValue, $key); openssl_free_key($key); $signature['ds:SignatureValue']['__v'] = base64_encode($signatureValue); $newElement = $element; foreach ($element as $tag => $item) { if ($tag == 'ds:Signature') { continue; } $newElement[$tag] = $item; if ($tag == 'saml:Issuer') { $newElement['ds:Signature'] = $signature; } } return $newElement; }
public function artifactResolutionService() { $postData = Corto_XmlToArray::xml2array(file_get_contents("php://input")); $artifact = $postData['SOAP-ENV:Body']['samlp:ArtifactResolve']['saml:Artifact'][Corto_XmlToArray::VALUE_PFX]; $this->_server->restartSession(sha1($artifact), 'artifact'); $message = $_SESSION['message']; session_destroy(); $element = $message[Corto_XmlToArray::TAG_NAME_PFX]; $artifactResponse = array('samlp:ArtifactResponse' => array('xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol', 'xmlns:saml' => 'urn:oasis:names:tc:SAML:2.0:assertion', 'ID' => $this->_server->getNewId(), 'Version' => '2.0', 'IssueInstant' => $this->_server->timeStamp(), 'InResponseTo' => $postData['SOAP-ENV:Body']['samlp:ArtifactResolve'][Corto_XmlToArray::ATTRIBUTE_PFX . 'ID'], 'saml:Issuer' => array(Corto_XmlToArray::VALUE_PFX => $this->_server->getCurrentEntityUrl()), $element => $message)); $this->_server->getBindingsModule()->soapResponse($artifactResponse); }