/** * Get temp directory path. * * This function retrieves the path to a directory where * temporary files can be saved. * * @return string Path to temp directory, without a trailing '/'. */ public static function getTempDir() { $globalConfig = SimpleSAML_Configuration::getInstance(); $tempDir = $globalConfig->getString('tempdir', '/tmp/simplesaml'); while (substr($tempDir, -1) === '/') { $tempDir = substr($tempDir, 0, -1); } if (!is_dir($tempDir)) { $ret = mkdir($tempDir, 0700, TRUE); if (!$ret) { throw new SimpleSAML_Error_Exception('Error creating temp dir ' . var_export($tempDir, TRUE) . ': ' . SimpleSAML_Utilities::getLastError()); } } elseif (function_exists('posix_getuid')) { /* Check that the owner of the temp diretory is the current user. */ $stat = lstat($tempDir); if ($stat['uid'] !== posix_getuid()) { throw new SimpleSAML_Error_Exception('Temp directory (' . var_export($tempDir, TRUE) . ') not owned by current user.'); } } return $tempDir; }
/** * Delete a metadata entry. * * @param string $entityId The entityId of the metadata entry. * @param string $set The metadata set this metadata entry belongs to. */ public function deleteMetadata($entityId, $set) { assert('is_string($entityId)'); assert('is_string($set)'); $filePath = $this->getMetadataPath($entityId, $set); if (!file_exists($filePath)) { SimpleSAML_Logger::warning('Attempted to erase non-existant metadata entry ' . var_export($entityId, TRUE) . ' in set ' . var_export($set, TRUE) . '.'); return; } $res = unlink($filePath); if ($res === FALSE) { SimpleSAML_Logger::error('Failed to delete file ' . $filePath . ': ' . SimpleSAML_Utilities::getLastError()); } }
/** * Overriding this function from the superclass SimpleSAML_Metadata_MetaDataStorageSource. * * This function retrieves metadata for the given entity id in the given set of metadata. * It will return NULL if it is unable to locate the metadata. * * This class implements this function using the getMetadataSet-function. A subclass should * override this function if it doesn't implement the getMetadataSet function, or if the * implementation of getMetadataSet is slow. * * @param $index The entityId or metaindex we are looking up. * @param $set The set we are looking for metadata in. * @return An associative array with metadata for the given entity, or NULL if we are unable to * locate the entity. */ public function getMetaData($index, $set) { assert('is_string($index)'); assert('is_string($set)'); if (!preg_match('@(https?://([-\\w\\.]+)+(:\\d+)?(/([\\w/_\\.]*(\\?\\S+)?)?)?)@', $index)) { SimpleSAML_Logger::info('MetaData - Handler.DynamicXML: EntityID/index [' . $index . '] does not look like a URL. Skipping.'); return NULL; } SimpleSAML_Logger::info('MetaData - Handler.DynamicXML: Loading metadata entity [' . $index . '] from [' . $set . ']'); /* Read from cache if possible. */ $data = $this->getFromCache($set, $index); if ($data !== NULL && array_key_exists('expires', $data) && $data['expires'] < time()) { /* Metadata has expired. */ $data = NULL; } if (isset($data)) { /* Metadata found in cache and not expired. */ SimpleSAML_Logger::debug('MetaData - Handler.DynamicXML: Using cached metadata.'); return $data; } SimpleSAML_Logger::debug('MetaData - Handler.DynamicXML: Downloading [' . $index . ']'); $xmldata = file_get_contents($index); if (empty($xmldata)) { throw new Exception('Error downloading metadata from "' . $index . '": ' . SimpleSAML_Utilities::getLastError()); } $entities = SimpleSAML_Metadata_SAMLParser::parseDescriptorsString($xmldata); SimpleSAML_Logger::debug('MetaData - Handler.DynamicXML: Completed parsing of [' . $index . '] Found [' . count($entities) . '] entries.'); if (count($entities) === 0) { throw new Exception('No entities found in "' . $index . '".'); } if (!array_key_exists($index, $entities)) { throw new Exception('No entity with correct entity id found in "' . $index . '".'); } $entity = $entities[$index]; $data = self::getParsedSet($entity, $set); if ($data === NULL) { throw new Exception('No metadata for set "' . $set . '" available from "' . $index . '".'); } $this->writeToCache($set, $index, $data); return $data; }
/** * This function sends the SOAP message to the service location and returns SOAP response * * @param SAML2_Message $msg The request that should be sent. * @param SimpleSAML_Configuration $srcMetadata The metadata of the issuer of the message. * @param SimpleSAML_Configuration $dstMetadata The metadata of the destination of the message. * @return SAML2_Message The response we received. * @throws Exception */ public function send(SAML2_Message $msg, SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata = NULL) { $issuer = $msg->getIssuer(); $ctxOpts = array('ssl' => array('capture_peer_cert' => TRUE)); /* Determine if we are going to do a MutualSSL connection between the IdP and SP - Shoaib */ if ($srcMetadata->hasValue('saml.SOAPClient.certificate')) { $cert = $srcMetadata->getValue('saml.SOAPClient.certificate'); if ($cert !== FALSE) { $ctxOpts['ssl']['local_cert'] = SimpleSAML_Utilities::resolveCert($srcMetadata->getString('saml.SOAPClient.certificate')); if ($srcMetadata->hasValue('saml.SOAPClient.privatekey_pass')) { $ctxOpts['ssl']['passphrase'] = $srcMetadata->getString('saml.SOAPClient.privatekey_pass'); } } } else { /* Use the SP certificate and privatekey if it is configured. */ $privateKey = SimpleSAML_Utilities::loadPrivateKey($srcMetadata); $publicKey = SimpleSAML_Utilities::loadPublicKey($srcMetadata); if ($privateKey !== NULL && $publicKey !== NULL && isset($publicKey['PEM'])) { $keyCertData = $privateKey['PEM'] . $publicKey['PEM']; $file = SimpleSAML_Utilities::getTempDir() . '/' . sha1($keyCertData) . '.pem'; if (!file_exists($file)) { SimpleSAML_Utilities::writeFile($file, $keyCertData); } $ctxOpts['ssl']['local_cert'] = $file; if (isset($privateKey['password'])) { $ctxOpts['ssl']['passphrase'] = $privateKey['password']; } } } /* Do peer certificate verification */ if ($dstMetadata !== NULL) { $peerPublicKeys = $dstMetadata->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"; } $peerCertFile = SimpleSAML_Utilities::getTempDir() . '/' . sha1($certData) . '.pem'; if (!file_exists($peerCertFile)) { SimpleSAML_Utilities::writeFile($peerCertFile, $certData); } /* Create ssl context */ $ctxOpts['ssl']['verify_peer'] = TRUE; $ctxOpts['ssl']['verify_depth'] = 1; $ctxOpts['ssl']['cafile'] = $peerCertFile; } $ctxOpts['http']['header'] = 'SOAPAction: "http://www.oasis-open.org/committees/security"' . "\n"; if ($this->username !== NULL && $this->password !== NULL) { /* Add HTTP Basic authentication header. */ $authData = $this->username . ':' . $this->password; $authData = base64_encode($authData); $ctxOpts['http']['header'] .= 'Authorization: Basic ' . $authData . "\n"; } if ($srcMetadata->hasValue('saml.SOAPClient.proxyhost')) { $options['proxy_host'] = $srcMetadata->getValue('saml.SOAPClient.proxyhost'); } if ($srcMetadata->hasValue('saml.SOAPClient.proxyport')) { $options['proxy_port'] = $srcMetadata->getValue('saml.SOAPClient.proxyport'); } $x = new SoapClient(NULL, $options); /* Add soap-envelopes */ $request = $msg->toSignedXML(); $request = self::START_SOAP_ENVELOPE . $request->ownerDocument->saveXML($request) . self::END_SOAP_ENVELOPE; SAML2_Utils::getContainer()->debugMessage($request, 'out'); $ctxOpts['http']['content'] = $request; $ctxOpts['http']['header'] .= 'Content-Type: text/xml; charset=utf-8' . "\n"; $ctxOpts['http']['method'] = 'POST'; $destination = $msg->getDestination(); /* Perform SOAP Request over HTTP */ $context = stream_context_create($ctxOpts); if ($context === NULL) { throw new Exception('Unable to create stream context'); } $soapresponsexml = @file_get_contents($destination, FALSE, $context); if ($soapresponsexml === FALSE) { throw new Exception('Error processing SOAP call: ' . SimpleSAML_Utilities::getLastError()); } SAML2_Utils::getContainer()->debugMessage($soapresponsexml, 'in'); /* Convert to SAML2_Message (DOMElement) */ try { $dom = SAML2_DOMDocumentFactory::fromString($soapresponsexml); } catch (SAML2_Exception_RuntimeException $e) { throw new Exception('Not a SOAP response.', 0, $e); } $soapfault = $this->getSOAPFault($dom); if (isset($soapfault)) { throw new Exception($soapfault); } /* Extract the message from the response */ $samlresponse = SAML2_Utils::xpQuery($dom->firstChild, '/soap-env:Envelope/soap-env:Body/*[1]'); $samlresponse = SAML2_Message::fromXML($samlresponse[0]); /* Add validator to message which uses the SSL context. */ self::addSSLValidator($samlresponse, $context); SAML2_Utils::getContainer()->getLogger()->debug("Valid ArtifactResponse received from IdP"); return $samlresponse; }
/** * Atomically write a file. * * This is a helper function for safely writing file data atomically. * It does this by writing the file data to a temporary file, and then * renaming this to the correct name. * * @param string $filename The name of the file. * @param string $data The data we should write to the file. */ public static function writeFile($filename, $data, $mode = 0600) { assert('is_string($filename)'); assert('is_string($data)'); assert('is_numeric($mode)'); $tmpFile = $filename . '.new.' . getmypid() . '.' . php_uname('n'); $res = file_put_contents($tmpFile, $data); if ($res === FALSE) { throw new SimpleSAML_Error_Exception('Error saving file ' . $tmpFile . ': ' . SimpleSAML_Utilities::getLastError()); } if (!self::isWindowsOS()) { $res = chmod($tmpFile, $mode); if ($res === FALSE) { unlink($tmpFile); throw new SimpleSAML_Error_Exception('Error changing file mode ' . $tmpFile . ': ' . SimpleSAML_Utilities::getLastError()); } } $res = rename($tmpFile, $filename); if ($res === FALSE) { unlink($tmpFile); throw new SimpleSAML_Error_Exception('Error renaming ' . $tmpFile . ' to ' . $filename . ': ' . SimpleSAML_Utilities::getLastError()); } }