224 lines
7.2 KiB
PHP
224 lines
7.2 KiB
PHP
<?php
|
|
/**
|
|
* Implementation of the SAML 2.0 SOAP binding.
|
|
*
|
|
* @author Shoaib Ali
|
|
* @package simpleSAMLphp
|
|
* @version $Id$
|
|
*/
|
|
class SAML2_SOAPClient {
|
|
|
|
const START_SOAP_ENVELOPE = '<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"><soap-env:Header/><soap-env:Body>';
|
|
const END_SOAP_ENVELOPE = '</soap-env:Body></soap-env: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;
|
|
}
|
|
|
|
}
|