/** * Receive a SAML 2 message sent using the HTTP-Artifact binding. * * Throws an exception if it is unable receive the message. * * @return SAML2_Message The received message. * @throws Exception */ public function receive() { if (array_key_exists('SAMLart', $_REQUEST)) { $artifact = base64_decode($_REQUEST['SAMLart']); $endpointIndex = bin2hex(substr($artifact, 2, 2)); $sourceId = bin2hex(substr($artifact, 4, 20)); } else { throw new Exception('Missing SAMLArt parameter.'); } $metadataHandler = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); $idpMetadata = $metadataHandler->getMetaDataConfigForSha1($sourceId, 'saml20-idp-remote'); if ($idpMetadata === NULL) { throw new Exception('No metadata found for remote provider with SHA1 ID: ' . var_export($sourceId, TRUE)); } $endpoint = NULL; foreach ($idpMetadata->getEndpoints('ArtifactResolutionService') as $ep) { if ($ep['index'] === hexdec($endpointIndex)) { $endpoint = $ep; break; } } if ($endpoint === NULL) { throw new Exception('No ArtifactResolutionService with the correct index.'); } SAML2_Utils::getContainer()->getLogger()->debug("ArtifactResolutionService endpoint being used is := " . $endpoint['Location']); //Construct the ArtifactResolve Request $ar = new SAML2_ArtifactResolve(); /* Set the request attributes */ $ar->setIssuer($this->spMetadata->getString('entityid')); $ar->setArtifact($_REQUEST['SAMLart']); $ar->setDestination($endpoint['Location']); require_once realpath(__DIR__ . '/../../../simplesamlphp/modules/saml/lib/Message.php'); /* Sign the request */ sspmod_saml_Message::addSign($this->spMetadata, $idpMetadata, $ar); // Shoaib - moved from the SOAPClient. $soap = new SAML2_SOAPClient(); // Send message through SoapClient /** @var SAML2_ArtifactResponse $artifactResponse */ $artifactResponse = $soap->send($ar, $this->spMetadata); if (!$artifactResponse->isSuccess()) { return false; } $xml = $artifactResponse->getAny(); if ($xml === NULL) { /* Empty ArtifactResponse - possibly because of Artifact replay? */ return NULL; } $samlResponse = SAML2_Message::fromXML($xml); $samlResponse->addValidator(array(get_class($this), 'validateSignature'), $artifactResponse); if (isset($_REQUEST['RelayState'])) { $samlResponse->setRelayState($_REQUEST['RelayState']); } return $samlResponse; }
/** * Receive a SAML 2 message sent using the HTTP-POST binding. * * Throws an exception if it is unable receive the message. * * @return SAML2_Message The received message. * @throws Exception */ public function receive() { $postText = file_get_contents('php://input'); if (empty($postText)) { throw new Exception('Invalid message received to AssertionConsumerService endpoint.'); } $document = SAML2_DOMDocumentFactory::fromString($postText); $xml = $document->firstChild; SAML2_Utils::getContainer()->debugMessage($xml, 'in'); $results = SAML2_Utils::xpQuery($xml, '/soap-env:Envelope/soap-env:Body/*[1]'); return SAML2_Message::fromXML($results[0]); }
/** * Receive a SAML 2 message sent using the HTTP-POST binding. * * Throws an exception if it is unable receive the message. * * @return SAML2_Message The received message. * @throws Exception */ public function receive() { if (array_key_exists('SAMLRequest', $_POST)) { $msg = $_POST['SAMLRequest']; } elseif (array_key_exists('SAMLResponse', $_POST)) { $msg = $_POST['SAMLResponse']; } else { throw new Exception('Missing SAMLRequest or SAMLResponse parameter.'); } $msg = base64_decode($msg); SAML2_Utils::getContainer()->debugMessage($msg, 'in'); $document = SAML2_DOMDocumentFactory::fromString($msg); $xml = $document->firstChild; $msg = SAML2_Message::fromXML($xml); if (array_key_exists('RelayState', $_POST)) { $msg->setRelayState($_POST['RelayState']); } return $msg; }
/** * Guess the current binding. * * This function guesses the current binding and creates an instance * of SAML2_Binding matching that binding. * * An exception will be thrown if it is unable to guess the binding. * * @return SAML2_Binding The binding. * @throws Exception */ public static function getCurrentBinding() { switch ($_SERVER['REQUEST_METHOD']) { case 'GET': if (array_key_exists('SAMLRequest', $_GET) || array_key_exists('SAMLResponse', $_GET)) { return new SAML2_HTTPRedirect(); } elseif (array_key_exists('SAMLart', $_GET)) { return new SAML2_HTTPArtifact(); } break; case 'POST': if (isset($_SERVER['CONTENT_TYPE'])) { $contentType = $_SERVER['CONTENT_TYPE']; $contentType = explode(';', $contentType); $contentType = $contentType[0]; /* Remove charset. */ } else { $contentType = NULL; } if (array_key_exists('SAMLRequest', $_POST) || array_key_exists('SAMLResponse', $_POST)) { return new SAML2_HTTPPost(); } elseif (array_key_exists('SAMLart', $_POST)) { return new SAML2_HTTPArtifact(); } elseif ($contentType === 'text/xml') { return new SAML2_SOAP(); } break; } $logger = SAML2_Utils::getContainer()->getLogger(); $logger->warning('Unable to find the SAML 2 binding used for this request.'); $logger->warning('Request method: ' . var_export($_SERVER['REQUEST_METHOD'], TRUE)); if (!empty($_GET)) { $logger->warning("GET parameters: '" . implode("', '", array_map('addslashes', array_keys($_GET))) . "'"); } if (!empty($_POST)) { $logger->warning("POST parameters: '" . implode("', '", array_map('addslashes', array_keys($_POST))) . "'"); } if (isset($_SERVER['CONTENT_TYPE'])) { $logger->warning('Content-Type: ' . var_export($_SERVER['CONTENT_TYPE'], TRUE)); } throw new Exception('Unable to find the current binding.'); }
/** * Decrypt an encrypted element. * * @param DOMElement $encryptedData The encrypted data. * @param XMLSecurityKey $inputKey The decryption key. * @param array $blacklist Blacklisted decryption algorithms. * @return DOMElement The decrypted element. * @throws Exception */ public static function decryptElement(DOMElement $encryptedData, XMLSecurityKey $inputKey, array $blacklist = array()) { try { return self::doDecryptElement($encryptedData, $inputKey, $blacklist); } catch (Exception $e) { /* * Something went wrong during decryption, but for security * reasons we cannot tell the user what failed. */ SAML2_Utils::getContainer()->getLogger()->error('Decryption failed: ' . $e->getMessage()); throw new Exception('Failed to decrypt XML element.'); } }
/** * Initialize a message. * * This constructor takes an optional parameter with a DOMElement. If this * parameter is given, the message will be initialized with data from that * XML element. * * If no XML element is given, the message is initialized with suitable * default values. * * @param string $tagName The tag name of the root element. * @param DOMElement|NULL $xml The input message. * @throws Exception */ protected function __construct($tagName, DOMElement $xml = NULL) { assert('is_string($tagName)'); $this->tagName = $tagName; $this->id = SAML2_Utils::getContainer()->generateId(); $this->issueInstant = time(); $this->certificates = array(); $this->validators = array(); if ($xml === NULL) { return; } if (!$xml->hasAttribute('ID')) { throw new Exception('Missing ID attribute on SAML message.'); } $this->id = $xml->getAttribute('ID'); if ($xml->getAttribute('Version') !== '2.0') { /* Currently a very strict check. */ throw new Exception('Unsupported version: ' . $xml->getAttribute('Version')); } $this->issueInstant = SAML2_Utils::xsDateTimeToTimestamp($xml->getAttribute('IssueInstant')); if ($xml->hasAttribute('Destination')) { $this->destination = $xml->getAttribute('Destination'); } if ($xml->hasAttribute('Consent')) { $this->consent = $xml->getAttribute('Consent'); } $issuer = SAML2_Utils::xpQuery($xml, './saml_assertion:Issuer'); if (!empty($issuer)) { $this->issuer = trim($issuer[0]->textContent); } /* Validate the signature element of the message. */ try { $sig = SAML2_Utils::validateElement($xml); if ($sig !== FALSE) { $this->certificates = $sig['Certificates']; $this->validators[] = array('Function' => array('SAML2_Utils', 'validateSignature'), 'Data' => $sig); } } catch (Exception $e) { /* Ignore signature validation errors. */ } $this->extensions = SAML2_XML_samlp_Extensions::getList($xml); }
/** * Retrieve the assertion. * * @param XMLSecurityKey $inputKey The key we should use to decrypt the assertion. * @param array $blacklist Blacklisted decryption algorithms. * @return SAML2_Assertion The decrypted assertion. */ public function getAssertion(XMLSecurityKey $inputKey, array $blacklist = array()) { $assertionXML = SAML2_Utils::decryptElement($this->encryptedData, $inputKey, $blacklist); SAML2_Utils::getContainer()->debugMessage($assertionXML, 'decrypt'); return new SAML2_Assertion($assertionXML); }
/** * Decrypt the NameId of the subject in the assertion. * * @param XMLSecurityKey $key The decryption key. * @param array $blacklist Blacklisted decryption algorithms. */ public function decryptNameId(XMLSecurityKey $key, array $blacklist = array()) { if ($this->encryptedNameId === NULL) { /* No NameID to decrypt. */ return; } $nameId = SAML2_Utils::decryptElement($this->encryptedNameId, $key, $blacklist); SAML2_Utils::getContainer()->debugMessage($nameId, 'decrypt'); $this->nameId = SAML2_Utils::parseNameId($nameId); $this->encryptedNameId = NULL; }
/** * launchkey_form - login form for wp-login.php * * @since 1.1.0 * * @param string $class A space separated list of classes to set on the "class" attribute of a containing DIV for the login button * @param string $id The value to set on the "id" attribute of a containing DIV for the login button * @param string $style A string of HTML style code tto set on the "style" attribute of a containing DIV for the login button */ public function launchkey_form($class = '', $id = '', $style = '') { if (isset($_GET['launchkey_error'])) { $this->wp_facade->_echo($this->template->render_template('error', array('error' => 'Error!', 'message' => 'The LaunchKey request was denied or an issue was detected during authentication. Please try again.'))); } elseif (isset($_GET['launchkey_ssl_error'])) { $this->wp_facade->_echo($this->template->render_template('error', array('error' => 'Error!', 'message' => 'There was an error trying to request the LaunchKey servers. If this persists you may need to disable SSL verification.'))); } elseif (isset($_GET['launchkey_security'])) { $this->wp_facade->_echo($this->template->render_template('error', array('error' => 'Error!', 'message' => 'There was a security issue detected and you have been logged out for your safety. Log back in to ensure a secure session.'))); } $container = SAML2_Utils::getContainer(); $request = new SAML2_AuthnRequest(); $request->setId($container->generateId()); //$request->setProviderName( parse_url( $this->wp_facade->home_url( '/' ), PHP_URL_HOST ) ); $request->setDestination($this->login_url); $request->setIssuer($this->entity_id); $request->setRelayState($this->wp_facade->admin_url()); $request->setAssertionConsumerServiceURL($this->wp_facade->wp_login_url()); $request->setProtocolBinding(SAML2_Const::BINDING_HTTP_POST); $request->setIsPassive(false); $request->setNameIdPolicy(array('Format' => SAML2_Const::NAMEID_PERSISTENT, 'AllowCreate' => true)); // Send it off using the HTTP-Redirect binding $binding = new SAML2_HTTPRedirect(); $binding->setDestination($this->login_url); $this->wp_facade->_echo($this->template->render_template('launchkey-form', array('class' => $class, 'id' => $id, 'style' => $style, 'login_url' => $binding->getRedirectURL($request), 'login_text' => 'Log in with', 'login_with_app_name' => 'LaunchKey', 'size' => in_array($this->wp_facade->get_locale(), array('fr_FR', 'es_ES')) ? 'small' : 'medium'))); }
/** * Validate a SOAP message against the certificate on the SSL connection. * * @param string $data The public key that was used on the connection. * @param XMLSecurityKey $key The key we should validate the certificate against. * @throws Exception */ public static function validateSSL($data, XMLSecurityKey $key) { assert('is_string($data)'); $keyInfo = openssl_pkey_get_details($key->key); if ($keyInfo === FALSE) { throw new Exception('Unable to get key details from XMLSecurityKey.'); } if (!isset($keyInfo['key'])) { throw new Exception('Missing key in public key details.'); } if ($keyInfo['key'] !== $data) { SAML2_Utils::getContainer()->getLogger()->debug('Key on SSL connection did not match key we validated against.'); return; } SAML2_Utils::getContainer()->getLogger()->debug('Message validated based on SSL certificate.'); }
/** * Receive a SAML 2 message sent using the HTTP-Redirect binding. * * Throws an exception if it is unable receive the message. * * @return SAML2_Message The received message. * @throws Exception */ public function receive() { $data = self::parseQuery(); if (array_key_exists('SAMLRequest', $data)) { $msg = $data['SAMLRequest']; } elseif (array_key_exists('SAMLResponse', $data)) { $msg = $data['SAMLResponse']; } else { throw new Exception('Missing SAMLRequest or SAMLResponse parameter.'); } if (array_key_exists('SAMLEncoding', $data)) { $encoding = $data['SAMLEncoding']; } else { $encoding = self::DEFLATE; } $msg = base64_decode($msg); switch ($encoding) { case self::DEFLATE: $msg = gzinflate($msg); break; default: throw new Exception('Unknown SAMLEncoding: ' . var_export($encoding, TRUE)); } SAML2_Utils::getContainer()->debugMessage($msg, 'in'); $document = new DOMDocument(); $document->loadXML($msg); $xml = $document->firstChild; $msg = SAML2_Message::fromXML($xml); if (array_key_exists('RelayState', $data)) { $msg->setRelayState($data['RelayState']); } if (array_key_exists('Signature', $data)) { if (!array_key_exists('SigAlg', $data)) { throw new Exception('Missing signature algorithm.'); } $signData = array('Signature' => $data['Signature'], 'SigAlg' => $data['SigAlg'], 'Query' => $data['SignedQuery']); $msg->addValidator(array(get_class($this), 'validateSignature'), $signData); } return $msg; }