protected function __construct() { /* Call the parent constructor in case it should become * necessary in the future. */ parent::__construct(); /* Initialize the php session handling. * * If session_id() returns a blank string, then we need * to call session start. Otherwise the session is already * started, and we should avoid calling session_start(). */ if (session_id() === '') { $config = SimpleSAML_Configuration::getInstance(); $cookiepath = $config->getBoolean('session.phpsession.limitedpath', FALSE) ? '/' . $config->getBaseURL() : '/'; session_set_cookie_params(0, $cookiepath, NULL, SimpleSAML_Utilities::isHTTPS()); $cookiename = $config->getString('session.phpsession.cookiename', NULL); if (!empty($cookiename)) { session_name($cookiename); } $savepath = $config->getString('session.phpsession.savepath', NULL); if (!empty($savepath)) { session_save_path($savepath); } if (!array_key_exists(session_name(), $_COOKIE)) { /* Session cookie unset - session id not set. Generate new (secure) session id. */ session_id(SimpleSAML_Utilities::stringToHex(SimpleSAML_Utilities::generateRandomBytes(16))); } session_start(); } }
/** * Retrieve the session id of saved in the session cookie. * * @return string The session id saved in the cookie. */ public function getCookieSessionId() { if (session_id() === '') { $session_cookie_params = session_get_cookie_params(); if ($session_cookie_params['secure'] && !SimpleSAML_Utilities::isHTTPS()) { throw new SimpleSAML_Error_Exception('Session start with secure cookie not allowed on http.'); } if (!self::hasSessionCookie()) { if (headers_sent()) { throw new SimpleSAML_Error_Exception('Cannot create new session - headers already sent.'); } /* Session cookie unset - session id not set. Generate new (secure) session id. */ $sessionId = SimpleSAML_Utilities::stringToHex(SimpleSAML_Utilities::generateRandomBytes(16)); SimpleSAML_Session::createSession($sessionId); session_id($sessionId); } session_start(); } return session_id(); }
/** * Will return https://sp.example.org */ public static function selfURLhost() { $currenthost = self::getSelfHost(); if (SimpleSAML_Utilities::isHTTPS()) { $protocol = 'https'; } else { $protocol = 'http'; } $portnumber = $_SERVER["SERVER_PORT"]; $port = ':' . $portnumber; if ($protocol == 'http') { if ($portnumber == '80') { $port = ''; } } elseif ($protocol == 'https') { if ($portnumber == '443') { $port = ''; } } $querystring = ''; return $protocol . "://" . $currenthost . $port; }
/** * Set a session cookie. * * @param string $name The name of the session cookie. * @param string|NULL $value The value of the cookie. Set to NULL to delete the cookie. */ public function setCookie($name, $value) { assert('is_string($name)'); assert('is_string($value) || is_null($value)'); $params = $this->getCookieParams(); // Do not set secure cookie if not on HTTPS if ($params['secure'] && !SimpleSAML_Utilities::isHTTPS()) { SimpleSAML_Logger::warning('Setting secure cookie on http not allowed.'); return; } if ($value === NULL) { $expire = time() - 365 * 24 * 60 * 60; } elseif ($params['lifetime'] === 0) { $expire = 0; } else { $expire = time() + $params['lifetime']; } if (!setcookie($name, $value, $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly'])) { throw new SimpleSAML_Error_Exception('Error setting cookie - headers already sent.'); } }
/** * Build an assertion based on information in the metadata. * * @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP. * @param SimpleSAML_Configuration $spMetadata The metadata of the SP. * @param array &$state The state array with information about the request. * @return SAML2_Assertion The assertion. */ private static function buildAssertion(SimpleSAML_Configuration $idpMetadata, SimpleSAML_Configuration $spMetadata, array &$state) { assert('isset($state["Attributes"])'); assert('isset($state["saml:ConsumerURL"])'); $signAssertion = $spMetadata->getBoolean('saml20.sign.assertion', NULL); if ($signAssertion === NULL) { $signAssertion = $idpMetadata->getBoolean('saml20.sign.assertion', TRUE); } $config = SimpleSAML_Configuration::getInstance(); $a = new SAML2_Assertion(); if ($signAssertion) { sspmod_saml_Message::addSign($idpMetadata, $spMetadata, $a); } $a->setIssuer($idpMetadata->getString('entityid')); $a->setValidAudiences(array($spMetadata->getString('entityid'))); $a->setNotBefore(time() - 30); $assertionLifetime = $spMetadata->getInteger('assertion.lifetime', NULL); if ($assertionLifetime === NULL) { $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300); } $a->setNotOnOrAfter(time() + $assertionLifetime); if (isset($state['saml:AuthnContextClassRef'])) { $a->setAuthnContext($state['saml:AuthnContextClassRef']); } else { $a->setAuthnContext(SAML2_Const::AC_PASSWORD); } if (isset($state['AuthnInstant'])) { $a->setAuthnInstant($state['AuthnInstant']); } else { /* For backwards compatibility. Remove in version 1.8. */ $session = SimpleSAML_Session::getInstance(); $a->setAuthnInstant($session->getAuthnInstant()); } $sessionLifetime = $config->getInteger('session.duration', 8 * 60 * 60); $a->setSessionNotOnOrAfter(time() + $sessionLifetime); $a->setSessionIndex(SimpleSAML_Utilities::generateID()); $sc = new SAML2_XML_saml_SubjectConfirmation(); $sc->SubjectConfirmationData = new SAML2_XML_saml_SubjectConfirmationData(); $sc->SubjectConfirmationData->NotOnOrAfter = time() + $assertionLifetime; $sc->SubjectConfirmationData->Recipient = $state['saml:ConsumerURL']; $sc->SubjectConfirmationData->InResponseTo = $state['saml:RequestId']; /* ProtcolBinding of SP's <AuthnRequest> overwrites IdP hosted metadata configuration. */ $hokAssertion = NULL; if ($state['saml:Binding'] === SAML2_Const::BINDING_HOK_SSO) { $hokAssertion = TRUE; } if ($hokAssertion === NULL) { $hokAssertion = $idpMetadata->getBoolean('saml20.hok.assertion', FALSE); } if ($hokAssertion) { /* Holder-of-Key */ $sc->Method = SAML2_Const::CM_HOK; if (SimpleSAML_Utilities::isHTTPS()) { if (isset($_SERVER['SSL_CLIENT_CERT']) && !empty($_SERVER['SSL_CLIENT_CERT'])) { /* Extract certificate data (if this is a certificate). */ $clientCert = $_SERVER['SSL_CLIENT_CERT']; $pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m'; if (preg_match($pattern, $clientCert, $matches)) { /* We have a client certificate from the browser which we add to the HoK assertion. */ $x509Certificate = new SAML2_XML_ds_X509Certificate(); $x509Certificate->certificate = str_replace(array("\r", "\n", " "), '', $matches[1]); $x509Data = new SAML2_XML_ds_X509Data(); $x509Data->data[] = $x509Certificate; $keyInfo = new SAML2_XML_ds_KeyInfo(); $keyInfo->info[] = $x509Data; $sc->SubjectConfirmationData->info[] = $keyInfo; } else { throw new SimpleSAML_Error_Exception('Error creating HoK assertion: No valid client certificate provided during TLS handshake with IdP'); } } else { throw new SimpleSAML_Error_Exception('Error creating HoK assertion: No client certificate provided during TLS handshake with IdP'); } } else { throw new SimpleSAML_Error_Exception('Error creating HoK assertion: No HTTPS connection to IdP, but required for Holder-of-Key SSO'); } } else { /* Bearer */ $sc->Method = SAML2_Const::CM_BEARER; } $a->setSubjectConfirmation(array($sc)); /* Add attributes. */ if ($spMetadata->getBoolean('simplesaml.attributes', TRUE)) { $attributeNameFormat = self::getAttributeNameFormat($idpMetadata, $spMetadata); $a->setAttributeNameFormat($attributeNameFormat); $attributes = self::encodeAttributes($idpMetadata, $spMetadata, $state['Attributes']); $a->setAttributes($attributes); } /* Generate the NameID for the assertion. */ if (isset($state['saml:NameIDFormat'])) { $nameIdFormat = $state['saml:NameIDFormat']; } else { $nameIdFormat = NULL; } if ($nameIdFormat === NULL || !isset($state['saml:NameID'][$nameIdFormat])) { /* Either not set in request, or not set to a format we supply. Fall back to old generation method. */ $nameIdFormat = $spMetadata->getString('NameIDFormat', 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'); } if (isset($state['saml:NameID'][$nameIdFormat])) { $nameId = $state['saml:NameID'][$nameIdFormat]; $nameId['Format'] = $nameIdFormat; } else { $spNameQualifier = $spMetadata->getString('SPNameQualifier', NULL); if ($spNameQualifier === NULL) { $spNameQualifier = $spMetadata->getString('entityid'); } if ($nameIdFormat === SAML2_Const::NAMEID_TRANSIENT) { /* generate a random id */ $nameIdValue = SimpleSAML_Utilities::generateID(); } else { /* this code will end up generating either a fixed assigned id (via nameid.attribute) or random id if not assigned/configured */ $nameIdValue = self::generateNameIdValue($idpMetadata, $spMetadata, $state); if ($nameIdValue === NULL) { SimpleSAML_Logger::warning('Falling back to transient NameID.'); $nameIdFormat = SAML2_Const::NAMEID_TRANSIENT; $nameIdValue = SimpleSAML_Utilities::generateID(); } } $nameId = array('Format' => $nameIdFormat, 'Value' => $nameIdValue, 'SPNameQualifier' => $spNameQualifier); } $state['saml:idp:NameID'] = $nameId; $a->setNameId($nameId); $encryptNameId = $spMetadata->getBoolean('nameid.encryption', NULL); if ($encryptNameId === NULL) { $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', FALSE); } if ($encryptNameId) { $a->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata)); } return $a; }
/** * Process an assertion in a response. * * Will throw an exception if it is invalid. * * @param SimpleSAML_Configuration $spMetadata The metadata of the service provider. * @param SimpleSAML_Configuration $idpMetadata The metadata of the identity provider. * @param SAML2_Response $response The response containing the assertion. * @param SAML2_Assertion|SAML2_EncryptedAssertion $assertion The assertion. * @param bool $responseSigned Whether the response is signed. * @return SAML2_Assertion The assertion, if it is valid. */ private static function processAssertion(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata, SAML2_Response $response, $assertion, $responseSigned) { assert('$assertion instanceof SAML2_Assertion || $assertion instanceof SAML2_EncryptedAssertion'); assert('is_bool($responseSigned)'); $assertion = self::decryptAssertion($idpMetadata, $spMetadata, $assertion); if (!self::checkSign($idpMetadata, $assertion)) { if (!$responseSigned) { throw new SimpleSAML_Error_Exception('Neither the assertion nor the response was signed.'); } } /* At least one valid signature found. */ $currentURL = SimpleSAML_Utilities::selfURLNoQuery(); /* Check various properties of the assertion. */ $notBefore = $assertion->getNotBefore(); if ($notBefore !== NULL && $notBefore > time() + 60) { throw new SimpleSAML_Error_Exception('Received an assertion that is valid in the future. Check clock synchronization on IdP and SP.'); } $notOnOrAfter = $assertion->getNotOnOrAfter(); if ($notOnOrAfter !== NULL && $notOnOrAfter <= time() - 60) { throw new SimpleSAML_Error_Exception('Received an assertion that has expired. Check clock synchronization on IdP and SP.'); } $sessionNotOnOrAfter = $assertion->getSessionNotOnOrAfter(); if ($sessionNotOnOrAfter !== NULL && $sessionNotOnOrAfter <= time() - 60) { throw new SimpleSAML_Error_Exception('Received an assertion with a session that has expired. Check clock synchronization on IdP and SP.'); } $validAudiences = $assertion->getValidAudiences(); if ($validAudiences !== NULL) { $spEntityId = $spMetadata->getString('entityid'); if (!in_array($spEntityId, $validAudiences, TRUE)) { $candidates = '[' . implode('], [', $validAudiences) . ']'; throw new SimpleSAML_Error_Exception('This SP [' . $spEntityId . '] is not a valid audience for the assertion. Candidates were: ' . $candidates); } } $found = FALSE; $lastError = 'No SubjectConfirmation element in Subject.'; foreach ($assertion->getSubjectConfirmation() as $sc) { if ($sc->Method !== SAML2_Const::CM_BEARER && $sc->Method !== SAML2_Const::CM_HOK) { $lastError = 'Invalid Method on SubjectConfirmation: ' . var_export($sc->Method, TRUE); continue; } /* Is SSO with HoK enabled? IdP remote metadata overwrites SP metadata configuration. */ $hok = $idpMetadata->getBoolean('saml20.hok.assertion', NULL); if ($hok === NULL) { $hok = $spMetadata->getBoolean('saml20.hok.assertion', FALSE); } if ($sc->Method === SAML2_Const::CM_BEARER && $hok) { $lastError = 'Bearer SubjectConfirmation received, but Holder-of-Key SubjectConfirmation needed'; continue; } if ($sc->Method === SAML2_Const::CM_HOK && !$hok) { $lastError = 'Holder-of-Key SubjectConfirmation received, but the Holder-of-Key profile is not enabled.'; continue; } $scd = $sc->SubjectConfirmationData; if ($sc->Method === SAML2_Const::CM_HOK) { /* Check HoK Assertion */ if (SimpleSAML_Utilities::isHTTPS() === FALSE) { $lastError = 'No HTTPS connection, but required for Holder-of-Key SSO'; continue; } if (isset($_SERVER['SSL_CLIENT_CERT']) && empty($_SERVER['SSL_CLIENT_CERT'])) { $lastError = 'No client certificate provided during TLS Handshake with SP'; continue; } /* Extract certificate data (if this is a certificate). */ $clientCert = $_SERVER['SSL_CLIENT_CERT']; $pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m'; if (preg_match($pattern, $clientCert, $matches) === FALSE) { $lastError = 'No valid client certificate provided during TLS Handshake with SP'; continue; } /* We have a valid client certificate from the browser. */ $clientCert = str_replace(array("\r", "\n", " "), '', $matches[1]); foreach ($scd->info as $thing) { if ($thing instanceof SAML2_XML_ds_KeyInfo) { $keyInfo[] = $thing; } } if (count($keyInfo) != 1) { $lastError = 'Error validating Holder-of-Key assertion: Only one <ds:KeyInfo> element in <SubjectConfirmationData> allowed'; continue; } foreach ($keyInfo[0]->info as $thing) { if ($thing instanceof SAML2_XML_ds_X509Data) { $x509data[] = $thing; } } if (count($x509data) != 1) { $lastError = 'Error validating Holder-of-Key assertion: Only one <ds:X509Data> element in <ds:KeyInfo> within <SubjectConfirmationData> allowed'; continue; } foreach ($x509data[0]->data as $thing) { if ($thing instanceof SAML2_XML_ds_X509Certificate) { $x509cert[] = $thing; } } if (count($x509cert) != 1) { $lastError = 'Error validating Holder-of-Key assertion: Only one <ds:X509Certificate> element in <ds:X509Data> within <SubjectConfirmationData> allowed'; continue; } $HoKCertificate = $x509cert[0]->certificate; if ($HoKCertificate !== $clientCert) { $lastError = 'Provided client certificate does not match the certificate bound to the Holder-of-Key assertion'; continue; } } if ($scd->NotBefore && $scd->NotBefore > time() + 60) { $lastError = 'NotBefore in SubjectConfirmationData is in the future: ' . $scd->NotBefore; continue; } if ($scd->NotOnOrAfter && $scd->NotOnOrAfter <= time() - 60) { $lastError = 'NotOnOrAfter in SubjectConfirmationData is in the past: ' . $scd->NotOnOrAfter; continue; } if ($scd->Recipient !== NULL && $scd->Recipient !== $currentURL) { $lastError = 'Recipient in SubjectConfirmationData does not match the current URL. Recipient is ' . var_export($scd->Recipient, TRUE) . ', current URL is ' . var_export($currentURL, TRUE) . '.'; continue; } if ($scd->InResponseTo !== NULL && $response->getInResponseTo() !== NULL && $scd->InResponseTo !== $response->getInResponseTo()) { $lastError = 'InResponseTo in SubjectConfirmationData does not match the Response. Response has ' . var_export($response->getInResponseTo(), TRUE) . ', SubjectConfirmationData has ' . var_export($scd->InResponseTo, TRUE) . '.'; continue; } $found = TRUE; break; } if (!$found) { throw new SimpleSAML_Error_Exception('Error validating SubjectConfirmation in Assertion: ' . $lastError); } /* As far as we can tell, the assertion is valid. */ /* Maybe we need to base64 decode the attributes in the assertion? */ if ($idpMetadata->getBoolean('base64attributes', FALSE)) { $attributes = $assertion->getAttributes(); $newAttributes = array(); foreach ($attributes as $name => $values) { $newAttributes[$name] = array(); foreach ($values as $value) { foreach (explode('_', $value) as $v) { $newAttributes[$name][] = base64_decode($v); } } } $assertion->setAttributes($newAttributes); } /* Decrypt the NameID element if it is encrypted. */ if ($assertion->isNameIdEncrypted()) { try { $keys = self::getDecryptionKeys($idpMetadata, $spMetadata); } catch (Exception $e) { throw new SimpleSAML_Error_Exception('Error decrypting NameID: ' . $e->getMessage()); } $blacklist = self::getBlacklistedAlgorithms($idpMetadata, $spMetadata); $lastException = NULL; foreach ($keys as $i => $key) { try { $assertion->decryptNameId($key, $blacklist); SimpleSAML_Logger::debug('Decryption with key #' . $i . ' succeeded.'); $lastException = NULL; break; } catch (Exception $e) { SimpleSAML_Logger::debug('Decryption with key #' . $i . ' failed with exception: ' . $e->getMessage()); $lastException = $e; } } if ($lastException !== NULL) { throw $lastException; } } return $assertion; }
/** * Helper function for setting a cookie. * * @param string $name Name of the cookie. * @param string|null $value Value of the cookie. Set this to null to * delete the cookie. * * @return void */ private function _setConsentCookie($name, $value) { assert('is_string($name)'); assert('is_string($value) || is_null($value)'); $globalConfig = SimpleSAML_Configuration::getInstance(); $params = array('lifetime' => 90 * 24 * 60 * 60, 'path' => '/' . $globalConfig->getBaseURL(), 'httponly' => FALSE); if (SimpleSAML_Utilities::isHTTPS()) { /* Enable secure cookie for https-requests. */ $params['secure'] = true; } else { $params['secure'] = false; } SimpleSAML_Utilities::setCookie($name, $value, $params, FALSE); }
<?php /* Load simpleSAMLphp, configuration */ $config = SimpleSAML_Configuration::getInstance(); $session = SimpleSAML_Session::getInstance(); /* Check if valid local session exists.. */ if ($config->getBoolean('admin.protectindexpage', false)) { SimpleSAML_Utilities::requireAdmin(); } $loginurl = SimpleSAML_Utilities::getAdminLoginURL(); $isadmin = SimpleSAML_Utilities::isAdmin(); $warnings = array(); if (!SimpleSAML_Utilities::isHTTPS()) { $warnings[] = '{core:frontpage:warnings_https}'; } if ($config->getValue('secretsalt') === 'defaultsecretsalt') { $warnings[] = '{core:frontpage:warnings_secretsalt}'; } if (extension_loaded('suhosin')) { $suhosinLength = ini_get('suhosin.get.max_value_length'); if (empty($suhosinLength) || (int) $suhosinLength < 2048) { $warnings[] = '{core:frontpage:warnings_suhosin_url_length}'; } } $links = array(); $links_welcome = array(); $links_config = array(); $links_auth = array(); $links_federation = array(); $links_config[] = array('href' => SimpleSAML_Utilities::getBaseURL() . 'example-simple/hostnames.php?dummy=1', 'text' => '{core:frontpage:link_diagnostics}'); $links_config[] = array('href' => SimpleSAML_Utilities::getBaseURL() . 'admin/phpinfo.php', 'text' => '{core:frontpage:link_phpinfo}');
/** * Helper function for setting a cookie. * * @param string $name Name of the cookie. * @param string|NULL $value Value of the cookie. Set this to NULL to delete the cookie. */ private function setConsentCookie($name, $value) { assert('is_string($name)'); assert('is_string($value)'); if ($value === NULL) { $expire = 1; /* Delete by setting expiry in the past. */ $value = ''; } else { $expire = time() + 90 * 24 * 60 * 60; } if (SimpleSAML_Utilities::isHTTPS()) { /* Enable secure cookie for https-requests. */ $secure = TRUE; } else { $secure = FALSE; } $globalConfig = SimpleSAML_Configuration::getInstance(); $path = '/' . $globalConfig->getBaseURL(); setcookie($name, $value, $expire, $path, NULL, $secure); }
/** * Retrieve the session id of saved in the session cookie. * * @return string The session id saved in the cookie. */ public function getCookieSessionId() { if (session_id() === '') { if (!self::hasSessionCookie()) { return self::newSessionId(); } $session_cookie_params = session_get_cookie_params(); if ($session_cookie_params['secure'] && !SimpleSAML_Utilities::isHTTPS()) { throw new SimpleSAML_Error_Exception('Session start with secure cookie not allowed on http.'); } session_start(); } return session_id(); }
SimpleSAML_Utilities::redirect('/' . $globalConfig->getBaseURL() . 'saml2/sp/initSSO.php', array('RelayState' => SimpleSAML_Utilities::selfURL())); } break; case 'shib13': if (!$session->isValid('shib13')) { SimpleSAML_Utilities::redirect('/' . $globalConfig->getBaseURL() . 'shib13/sp/initSSO.php', array('RelayState' => SimpleSAML_Utilities::selfURL())); } break; default: /* Should never happen, as the login method is checked in the AuthMemCookie class. */ throw new Exception('Invalid login method.'); } /* Generate session id and save it in a cookie. */ $sessionID = SimpleSAML_Utilities::generateID(); $cookieName = $amc->getCookieName(); setcookie($cookieName, $sessionID, 0, '/', NULL, SimpleSAML_Utilities::isHTTPS(), TRUE); /* Generate the authentication information. */ $attributes = $session->getAttributes(); $authData = array(); /* Username. */ $usernameAttr = $amc->getUsernameAttr(); if (!array_key_exists($usernameAttr, $attributes)) { throw new Exception('The user doesn\'t have an attribute named \'' . $usernameAttr . '\'. This attribute is expected to contain the username.'); } $authData['UserName'] = $attributes[$usernameAttr]; /* Groups. */ $groupsAttr = $amc->getGroupsAttr(); if ($groupsAttr !== NULL) { if (!array_key_exists($groupsAttr, $attributes)) { throw new Exception('The user doesn\'t have an attribute named \'' . $groupsAttr . '\'. This attribute is expected to contain the groups the user is a member of.'); }
/** * This function logs the user out by deleting the session information from memcache. */ private function doLogout() { $cookieName = $this->getCookieName(); /* Check if we have a valid cookie. */ if (!array_key_exists($cookieName, $_COOKIE)) { return; } $sessionID = $_COOKIE[$cookieName]; /* Delete the session from memcache. */ $memcache = $this->getMemcache(); $memcache->delete($sessionID); /* Delete the session cookie. */ setcookie($cookieName, '', 1, '/', NULL, SimpleSAML_Utilities::isHTTPS(), TRUE); }