/** * Load private key from metadata. * * This function loads a private key from a metadata array. It searches for the * following elements: * 'privatekey' Name of a private key file in the cert-directory. * 'privatekey_pass' Password for the private key. * * It returns and array with the following elements: * 'PEM' Data for the private key, in PEM-format * 'password' Password for the private key. * * @param SimpleSAML_Configuration $metadata The metadata array the private key should be loaded from. * @param bool $required Whether the private key is required. If this is TRUE, a * missing key will cause an exception. Default is FALSE. * @param string $prefix The prefix which should be used when reading from the metadata * array. Defaults to ''. * @return array|NULL Extracted private key, or NULL if no private key is present. */ public static function loadPrivateKey(SimpleSAML_Configuration $metadata, $required = FALSE, $prefix = '') { assert('is_bool($required)'); assert('is_string($prefix)'); $file = $metadata->getString($prefix . 'privatekey', NULL); if ($file === NULL) { /* No private key found. */ if ($required) { throw new Exception('No private key found in metadata.'); } else { return NULL; } } $file = SimpleSAML_Utilities::resolveCert($file); $data = @file_get_contents($file); if ($data === FALSE) { throw new Exception('Unable to load private key from file "' . $file . '"'); } $ret = array('PEM' => $data); if ($metadata->hasValue($prefix . 'privatekey_pass')) { $ret['password'] = $metadata->getString($prefix . 'privatekey_pass'); } return $ret; }
public function validate() { assert('$this->dom instanceof DOMDocument'); if ($this->messageValidated) { /* This message was validated externally. */ return TRUE; } /* Validate the signature. */ $this->validator = new SimpleSAML_XML_Validator($this->dom, array('ResponseID', 'AssertionID')); // Get the issuer of the response. $issuer = $this->getIssuer(); /* Get the metadata of the issuer. */ $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); $md = $metadata->getMetaDataConfig($issuer, 'shib13-idp-remote'); $publicKeys = $md->getPublicKeys('signing'); if ($publicKeys !== NULL) { $certFingerprints = array(); foreach ($publicKeys as $key) { if ($key['type'] !== 'X509Certificate') { continue; } $certFingerprints[] = sha1(base64_decode($key['X509Certificate'])); } $this->validator->validateFingerprint($certFingerprints); } elseif ($md->hasValue('certFingerprint')) { $certFingerprints = $md->getArrayizeString('certFingerprint'); /* Validate the fingerprint. */ $this->validator->validateFingerprint($certFingerprints); } elseif ($md->hasValue('caFile')) { /* Validate against CA. */ $this->validator->validateCA(SimpleSAML_Utilities::resolveCert($md->getString('caFile'))); } else { throw new SimpleSAML_Error_Exception('Missing certificate in Shibboleth 1.3 IdP Remote metadata for identity provider [' . $issuer . '].'); } return true; }
/* Find the saml:Assertion element in the response. */ $assertions = $xpath->query('/wst:RequestSecurityTokenResponse/wst:RequestedSecurityToken/saml:Assertion'); if ($assertions->length === 0) { throw new Exception('Received a response without an assertion on the WS-Fed PRP handler.'); } if ($assertions->length > 1) { throw new Exception('The WS-Fed PRP handler currently only supports a single assertion in a response.'); } $assertion = $assertions->item(0); /* Find the entity id of the issuer. */ $idpEntityId = $assertion->getAttribute('Issuer'); /* Load the IdP metadata. */ $idpMetadata = $metadata->getMetaData($idpEntityId, 'wsfed-idp-remote'); /* Find the certificate used by the IdP. */ if (array_key_exists('certificate', $idpMetadata)) { $certFile = SimpleSAML_Utilities::resolveCert($idpMetadata['certificate']); } else { throw new Exception('Missing \'certificate\' metadata option in the \'wsfed-idp-remote\' metadata' . ' for the IdP \'' . $idpEntityId . '\'.'); } /* Load the certificate. */ $certData = file_get_contents($certFile); if ($certData === FALSE) { throw new Exception('Unable to load certificate file \'' . $certFile . '\' for wsfed-idp \'' . $idpEntityId . '\'.'); } /* Verify that the assertion is signed by the issuer. */ $validator = new SimpleSAML_XML_Validator($assertion, 'AssertionID', $certData); if (!$validator->isNodeValidated($assertion)) { throw new Exception('The assertion was not correctly signed by the WS-Fed IdP \'' . $idpEntityId . '\'.'); } /* Check time constraints of contitions (if present). */ foreach ($xpath->query('./saml:Conditions', $assertion) as $condition) {
} echo " /> <label for='gepiEnableIdpSaml20' style='cursor: pointer;'>Fournir une identification SAML 2.0</label>\n"; echo "<p>\n"; echo "<label for='sacocheUrl' style='cursor: pointer;'>Adresse du service qui va se connecter si possible en https (<em>exemple : https://localhost/mon-appli</em>) </label>\n"; echo "<input type='text' size='60' name='sacocheUrl' value='" . getSettingValue("sacocheUrl") . "' id='sacocheUrl' />\n<br/>"; echo "<label for='sacoche_base' style='cursor: pointer;'>Numéro de base sacoche (<em>laisser vide si votre installation de sacoche est mono établissement</em>)</label>\n"; echo "<input type='text' size='5' name='sacoche_base' value='" . getSettingValue("sacoche_base") . "' id='sacoche_base' />\n<br/>"; echo 'pour une configuration manuelle, modifier le fichier /lib/simplesaml/metadata/saml20-sp-remote.php'; try { require_once '../lib/simplesaml/lib/_autoload.php'; $config = SimpleSAML_Configuration::getConfig(); //$cert_file =SimpleSAML_Utilities::resolveCert('server.crt'); $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); $spMetadata = $metadata->getMetaDataConfig('gepi-idp', 'saml20-idp-hosted'); if ($spMetadata != null) { $cert_file = SimpleSAML_Utilities::resolveCert($spMetadata->getString('certificate')); $x509cert = file_get_contents($cert_file); echo '<br/>Empreinte (fingerprint) du certificat x509 (à renseigner dans votre logiciel founisseur de service) : '; $lines = explode("\n", $x509cert); $data = ''; foreach ($lines as $line) { /* Remove '\r' from end of line if present. */ $line = rtrim($line); if ($line === '-----BEGIN CERTIFICATE-----') { /* Delete junk from before the certificate. */ $data = ''; } elseif ($line === '-----END CERTIFICATE-----') { /* Ignore data after the certificate. */ break; } elseif ($line === '-----BEGIN PUBLIC KEY-----') { /* This isn't an X509 certificate. */
/** * Retrieve and parse the metadata. * * @return SAML2_XML_md_EntitiesDescriptor|SAML2_XML_md_EntityDescriptor|NULL * The downloaded metadata or NULL if we were unable to download or parse it. */ private function downloadMetadata() { SimpleSAML\Logger::debug($this->logLoc . 'Downloading metadata from ' . var_export($this->url, TRUE)); $context = array('ssl' => array()); if ($this->sslCAFile !== NULL) { $context['ssl']['cafile'] = SimpleSAML_Utilities::resolveCert($this->sslCAFile); SimpleSAML\Logger::debug($this->logLoc . 'Validating https connection against CA certificate(s) found in ' . var_export($context['ssl']['cafile'], TRUE)); $context['ssl']['verify_peer'] = TRUE; $context['ssl']['CN_match'] = parse_url($this->url, PHP_URL_HOST); } $data = SimpleSAML_Utilities::fetch($this->url, $context); if ($data === FALSE || $data === NULL) { SimpleSAML\Logger::error($this->logLoc . 'Unable to load metadata from ' . var_export($this->url, TRUE)); return NULL; } $doc = new DOMDocument(); $res = $doc->loadXML($data); if (!$res) { SimpleSAML\Logger::error($this->logLoc . 'Error parsing XML from ' . var_export($this->url, TRUE)); return NULL; } $root = SAML2_Utils::xpQuery($doc->firstChild, '/saml_metadata:EntityDescriptor|/saml_metadata:EntitiesDescriptor'); if (count($root) === 0) { SimpleSAML\Logger::error($this->logLoc . 'No <EntityDescriptor> or <EntitiesDescriptor> in metadata from ' . var_export($this->url, TRUE)); return NULL; } if (count($root) > 1) { SimpleSAML\Logger::error($this->logLoc . 'More than one <EntityDescriptor> or <EntitiesDescriptor> in metadata from ' . var_export($this->url, TRUE)); return NULL; } $root = $root[0]; try { if ($root->localName === 'EntityDescriptor') { $md = new SAML2_XML_md_EntityDescriptor($root); } else { $md = new SAML2_XML_md_EntitiesDescriptor($root); } } catch (Exception $e) { SimpleSAML\Logger::error($this->logLoc . 'Unable to parse metadata from ' . var_export($this->url, TRUE) . ': ' . $e->getMessage()); return NULL; } if ($this->certificate !== NULL) { $file = SimpleSAML_Utilities::resolveCert($this->certificate); $certData = file_get_contents($file); if ($certData === FALSE) { throw new SimpleSAML_Error_Exception('Error loading certificate from ' . var_export($file, TRUE)); } // Extract the public key from the certificate for validation $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'public')); $key->loadKey($file, TRUE); if (!$md->validate($key)) { SimpleSAML\Logger::error($this->logLoc . 'Error validating signature on metadata.'); return NULL; } SimpleSAML\Logger::debug($this->logLoc . 'Validated signature on metadata from ' . var_export($this->url, TRUE)); } return $md; }
/** * Check the signature on a SAML2 message or assertion. * * @param SimpleSAML_Configuration $srcMetadata The metadata of the sender. * @param SAML2_SignedElement $element Either a SAML2_Response or a SAML2_Assertion. */ public static function checkSign(SimpleSAML_Configuration $srcMetadata, SAML2_SignedElement $element) { $certificates = $element->getCertificates(); SimpleSAML_Logger::debug('Found ' . count($certificates) . ' certificates in ' . get_class($element)); /* Find the certificate that should verify signatures by this entity. */ $certArray = SimpleSAML_Utilities::loadPublicKey($srcMetadata, FALSE); if ($certArray !== NULL) { if (array_key_exists('PEM', $certArray)) { $pemCert = $certArray['PEM']; } else { /* * We don't have the full certificate stored. Try to find it * in the message or the assertion instead. */ if (count($certificates) === 0) { /* We need the full certificate in order to match it against the fingerprint. */ SimpleSAML_Logger::debug('No certificate in message when validating against fingerprint.'); return FALSE; } $certFingerprints = $certArray['certFingerprint']; if (count($certFingerprints) === 0) { /* For some reason, we have a certFingerprint entry without any fingerprints. */ throw new SimpleSAML_Error_Exception('certFingerprint array was empty.'); } $pemCert = self::findCertificate($certFingerprints, $certificates); } } else { /* Attempt CA validation. */ $caFile = $srcMetadata->getString('caFile', NULL); if ($caFile === NULL) { throw new SimpleSAML_Error_Exception('Missing certificate in metadata for ' . var_export($srcMetadata->getString('entityid'), TRUE)); } $caFile = SimpleSAML_Utilities::resolveCert($caFile); if (count($certificates) === 0) { /* We need the full certificate in order to check it against the CA file. */ SimpleSAML_Logger::debug('No certificate in message when validating with CA.'); return FALSE; } /* We assume that it is the first certificate that was used to sign the message. */ $pemCert = "-----BEGIN CERTIFICATE-----\n" . chunk_split($certificates[0], 64) . "-----END CERTIFICATE-----\n"; SimpleSAML_Utilities::validateCA($pemCert, $caFile); } /* Extract the public key from the certificate for validation. */ $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'public')); $key->loadKey($pemCert); /* * Make sure that we have a valid signature on either the response * or the assertion. */ return $element->validate($key); }
/** * 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_Utilities::debugMessage($msgStr, '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_Utilities::getTempDir() . '/' . sha1($certData) . '.crt'; if (!file_exists($file)) { SimpleSAML_Utilities::writeFile($file, $certData); } $spKeyCertFile = SimpleSAML_Utilities::resolveCert($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_Utilities::fetch($url, $opts); if ($response === FALSE) { throw new SimpleSAML_Error_Exception('Failed to retrieve assertion from IdP.'); } SimpleSAML_Utilities::debugMessage($response, 'in'); /* Find the response in the SOAP message. */ $response = self::extractResponse($response); return $response; }
/** * Get public key from metadata. * * @param string|NULL $use The purpose this key can be used for. (encryption or signing). * @param bool $required Whether the public key is required. If this is TRUE, a * missing key will cause an exception. Default is FALSE. * @param string $prefix The prefix which should be used when reading from the metadata * array. Defaults to ''. * @return array|NULL Public key data, or NULL if no public key or was found. */ public function getPublicKeys($use = NULL, $required = FALSE, $prefix = '') { assert('is_bool($required)'); assert('is_string($prefix)'); if ($this->hasValue($prefix . 'keys')) { $ret = array(); foreach ($this->getArray($prefix . 'keys') as $key) { if ($use !== NULL && isset($key[$use]) && !$key[$use]) { continue; } if (isset($key['X509Certificate'])) { /* Strip whitespace from key. */ $key['X509Certificate'] = preg_replace('/\\s+/', '', $key['X509Certificate']); } $ret[] = $key; } if (!empty($ret)) { return $ret; } } elseif ($this->hasValue($prefix . 'certData')) { $certData = $this->getString($prefix . 'certData'); $certData = preg_replace('/\\s+/', '', $certData); return array(array('encryption' => TRUE, 'signing' => TRUE, 'type' => 'X509Certificate', 'X509Certificate' => $certData)); } elseif ($this->hasValue($prefix . 'certificate')) { $file = $this->getString($prefix . 'certificate'); $file = SimpleSAML_Utilities::resolveCert($file); $data = @file_get_contents($file); if ($data === FALSE) { throw new Exception($this->location . ': Unable to load certificate/public key from file "' . $file . '".'); } /* Extract certificate data (if this is a certificate). */ $pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m'; if (!preg_match($pattern, $data, $matches)) { throw new SimpleSAML_Error_Exception($this->location . ': Could not find PEM encoded certificate in "' . $file . '".'); } $certData = preg_replace('/\\s+/', '', $matches[1]); return array(array('encryption' => TRUE, 'signing' => TRUE, 'type' => 'X509Certificate', 'X509Certificate' => $certData)); } if ($required) { throw new SimpleSAML_Error_Exception($this->location . ': Missing certificate in metadata.'); } else { return NULL; } }
/** * This function sends the SOAP message to the service location and returns SOAP response * * @param SAML2_Message $m 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. */ 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')) { $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; } $context = stream_context_create($ctxOpts); if ($context === NULL) { throw new Exception('Unable to create SSL stream context'); } $options = array('uri' => $issuer, 'location' => $msg->getDestination(), 'stream_context' => $context); $x = new SoapClient(NULL, $options); // Add soap-envelopes $request = $msg->toSignedXML(); $request = self::START_SOAP_ENVELOPE . $request->ownerDocument->saveXML($request) . self::END_SOAP_ENVELOPE; SimpleSAML_Utilities::debugMessage($request, 'out'); $action = 'http://www.oasis-open.org/committees/security'; $version = '1.1'; $destination = $msg->getDestination(); /* Perform SOAP Request over HTTP */ $soapresponsexml = $x->__doRequest($request, $destination, $action, $version); if ($soapresponsexml === NULL || $soapresponsexml === "") { throw new Exception('Empty SOAP response, check peer certificate.'); } SimpleSAML_Utilities::debugMessage($soapresponsexml, 'in'); // Convert to SAML2_Message (DOMElement) $dom = new DOMDocument(); if (!$dom->loadXML($soapresponsexml)) { throw new Exception('Not a SOAP response.'); } $soapfault = $this->getSOAPFault($dom); if (isset($soapfault)) { throw new Exception($soapfault); } //Extract the message from the response $xml = $dom->firstChild; /* Soap Envelope */ $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); SimpleSAML_Logger::debug("Valid ArtifactResponse received from IdP"); return $samlresponse; }
public static function sendResponse(array $state) { $spMetadata = $state["SPMetadata"]; $spEntityId = $spMetadata['entityid']; $spMetadata = SimpleSAML_Configuration::loadFromArray($spMetadata, '$metadata[' . var_export($spEntityId, TRUE) . ']'); $attributes = $state['Attributes']; $nameidattribute = $spMetadata->getValue('simplesaml.nameidattribute'); if (!empty($nameidattribute)) { if (!array_key_exists($nameidattribute, $attributes)) { throw new Exception('simplesaml.nameidattribute does not exist in resulting attribute set'); } $nameid = $attributes[$nameidattribute][0]; } else { $nameid = SimpleSAML_Utilities::generateID(); } $idp = SimpleSAML_IdP::getByState($state); $idpMetadata = $idp->getConfig(); $idpEntityId = $idpMetadata->getString('entityid'); $idp->addAssociation(array('id' => 'adfs:' . $spEntityId, 'Handler' => 'sspmod_adfs_IdP_ADFS', 'adfs:entityID' => $spEntityId)); $response = sspmod_adfs_IdP_ADFS::ADFS_GenerateResponse($idpEntityId, $spEntityId, $nameid, $attributes); $privateKeyFile = SimpleSAML_Utilities::resolveCert($idpMetadata->getString('privatekey')); $certificateFile = SimpleSAML_Utilities::resolveCert($idpMetadata->getString('certificate')); $wresult = sspmod_adfs_IdP_ADFS::ADFS_SignResponse($response, $privateKeyFile, $certificateFile); $wctx = $state['adfs:wctx']; sspmod_adfs_IdP_ADFS::ADFS_PostResponse($spMetadata->getValue('prp'), $wresult, $wctx); }
/** * 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; }
/** * Check the signature on a SAML2 message or assertion. * * @param SimpleSAML_Configuration $srcMetadata The metadata of the sender. * @param SAML2_SignedElement $element Either a SAML2_Response or a SAML2_Assertion. */ public static function checkSign(SimpleSAML_Configuration $srcMetadata, SAML2_SignedElement $element) { /* Find the public key that should verify signatures by this entity. */ $keys = $srcMetadata->getPublicKeys('signing'); if ($keys !== NULL) { $pemKeys = array(); foreach ($keys as $key) { switch ($key['type']) { case 'X509Certificate': $pemKeys[] = "-----BEGIN CERTIFICATE-----\n" . chunk_split($key['X509Certificate'], 64) . "-----END CERTIFICATE-----\n"; break; default: SimpleSAML_Logger::debug('Skipping unknown key type: ' . $key['type']); } } } elseif ($srcMetadata->hasValue('certFingerprint')) { $certFingerprint = $srcMetadata->getArrayizeString('certFingerprint'); foreach ($certFingerprint as &$fp) { $fp = strtolower(str_replace(':', '', $fp)); } $certificates = $element->getCertificates(); /* * We don't have the full certificate stored. Try to find it * in the message or the assertion instead. */ if (count($certificates) === 0) { /* We need the full certificate in order to match it against the fingerprint. */ SimpleSAML_Logger::debug('No certificate in message when validating against fingerprint.'); return FALSE; } else { SimpleSAML_Logger::debug('Found ' . count($certificates) . ' certificates in ' . get_class($element)); } $pemCert = self::findCertificate($certFingerprint, $certificates); $pemKeys = array($pemCert); } else { /* Attempt CA validation. */ $caFile = $srcMetadata->getString('caFile', NULL); if ($caFile === NULL) { throw new SimpleSAML_Error_Exception('Missing certificate in metadata for ' . var_export($srcMetadata->getString('entityid'), TRUE)); } $caFile = SimpleSAML_Utilities::resolveCert($caFile); if (count($certificates) === 0) { /* We need the full certificate in order to check it against the CA file. */ SimpleSAML_Logger::debug('No certificate in message when validating with CA.'); return FALSE; } /* We assume that it is the first certificate that was used to sign the message. */ $pemCert = "-----BEGIN CERTIFICATE-----\n" . chunk_split($certificates[0], 64) . "-----END CERTIFICATE-----\n"; SimpleSAML_Utilities::validateCA($pemCert, $caFile); $pemKeys = array($pemCert); } SimpleSAML_Logger::debug('Has ' . count($pemKeys) . ' candidate keys for validation.'); $lastException = NULL; foreach ($pemKeys as $i => $pem) { $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'public')); $key->loadKey($pem); try { /* * Make sure that we have a valid signature on either the response * or the assertion. */ $res = $element->validate($key); if ($res) { SimpleSAML_Logger::debug('Validation with key #' . $i . ' succeeded.'); return TRUE; } SimpleSAML_Logger::debug('Validation with key #' . $i . ' failed without exception.'); } catch (Exception $e) { SimpleSAML_Logger::debug('Validation with key #' . $i . ' failed with exception: ' . $e->getMessage()); $lastException = $e; } } /* We were unable to validate the signature with any of our keys. */ if ($lastException !== NULL) { throw $lastException; } else { return FALSE; } }
/** * Signs the given metadata if metadata signing is enabled. * * @param $metadataString A string with the metadata. * @param $entityMetadata The metadata of the entity. * @param $type A string which describes the type entity this is, e.g. 'SAML 2 IdP' or 'Shib 1.3 SP'. * @return The $metadataString with the signature embedded. */ public static function sign($metadataString, $entityMetadata, $type) { $config = SimpleSAML_Configuration::getInstance(); /* Check if metadata signing is enabled. */ if (!self::isMetadataSigningEnabled($config, $entityMetadata, $type)) { return $metadataString; } /* Find the key & certificate which should be used to sign the metadata. */ $keyCertFiles = self::findKeyCert($config, $entityMetadata, $type); $keyFile = SimpleSAML_Utilities::resolveCert($keyCertFiles['privatekey']); if (!file_exists($keyFile)) { throw new Exception('Could not find private key file [' . $keyFile . '], which is needed to sign the metadata'); } $keyData = file_get_contents($keyFile); $certFile = SimpleSAML_Utilities::resolveCert($keyCertFiles['certificate']); if (!file_exists($certFile)) { throw new Exception('Could not find certificate file [' . $certFile . '], which is needed to sign the metadata'); } $certData = file_get_contents($certFile); /* Convert the metadata to a DOM tree. */ $xml = new DOMDocument(); if (!$xml->loadXML($metadataString)) { throw new Exception('Error parsing self-generated metadata.'); } /* Load the private key. */ $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'private')); if (array_key_exists('privatekey_pass', $keyCertFiles)) { $objKey->passphrase = $keyCertFiles['privatekey_pass']; } $objKey->loadKey($keyData, FALSE); /* Get the EntityDescriptor node we should sign. */ $rootNode = $xml->firstChild; /* Sign the metadata with our private key. */ if ($type == 'ADFS IdP') { $objXMLSecDSig = new sspmod_adfs_XMLSecurityDSig($metadataString); } else { $objXMLSecDSig = new XMLSecurityDSig(); } $objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N); $objXMLSecDSig->addReferenceList(array($rootNode), XMLSecurityDSig::SHA1, array('http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N), array('id_name' => 'ID')); $objXMLSecDSig->sign($objKey); /* Add the certificate to the signature. */ $objXMLSecDSig->add509Cert($certData, true); /* Add the signature to the metadata. */ $objXMLSecDSig->insertSignature($rootNode, $rootNode->firstChild); /* Return the DOM tree as a string. */ return $xml->saveXML(); }
/** * Add an extra certificate to the certificate chain in the signature. * * Extra certificates will be added to the certificate chain in the order they * are added. * * @param $file The file which contains the certificate, relative to the cert-directory. */ public function addCertificate($file) { assert('is_string($file)'); $certFile = SimpleSAML_Utilities::resolveCert($file); if (!file_exists($certFile)) { throw new Exception('Could not find extra certificate file "' . $certFile . '".'); } $certificate = file_get_contents($certFile); if ($certificate === FALSE) { throw new Exception('Unable to read extra certificate file "' . $certFile . '".'); } $this->extraCertificates[] = $certificate; }