'; const END_SOAP_ENVELOPE = ''; /** * 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')) { $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; } $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; } /** * Add a signature validator based on a SSL context. * * @param SAML2_Message $msg The message we should add a validator to. * @param resource $context The stream context. */ private static function addSSLValidator(SAML2_Message $msg, $context) { $options = stream_context_get_options($context); if (!isset($options['ssl']['peer_certificate'])) { return; } //$out = ''; //openssl_x509_export($options['ssl']['peer_certificate'], $out); $key = openssl_pkey_get_public($options['ssl']['peer_certificate']); if ($key === FALSE) { SimpleSAML_Logger::warning('Unable to get public key from peer certificate.'); return; } $keyInfo = openssl_pkey_get_details($key); if ($keyInfo === FALSE) { SimpleSAML_Logger::warning('Unable to get key details from public key.'); return; } if (!isset($keyInfo['key'])) { SimpleSAML_Logger::warning('Missing key in public key details.'); return; } $msg->addValidator(array('SAML2_SOAPClient', 'validateSSL'), $keyInfo['key']); } /** * 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. */ 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) { SimpleSAML_Logger::debug('Key on SSL connection did not match key we validated against.'); return FALSE; } SimpleSAML_Logger::debug('Message validated based on SSL certificate.'); } /* * Extracts the SOAP Fault from SOAP message * @param $soapmessage Soap response needs to be type DOMDocument * @return $soapfaultstring string|NULL */ private function getSOAPFault($soapmessage) { $soapfault = SAML2_Utils::xpQuery($soapmessage->firstChild, '/soap-env:Envelope/soap-env:Body/soap-env:Fault'); if (empty($soapfault)) { /* No fault. */ return NULL; } $soapfaultelement = $soapfault[0]; $soapfaultstring = "Unknown fault string found"; // There is a fault element but we havn't found out what the fault string is // find out the fault string $faultstringelement = SAML2_Utils::xpQuery($soapfaultelement, './soap-env:faultstring') ; if (!empty($faultstringelement)) { return $faultstringelement[0]->textContent; } return $soapfaultstring; } }