After few days of google -ing/trying/loosing hair I still can't find solution for this so please help :)
Short info:
I need to work with WCF service from PHP (SOAP client). It uses wsHttpBinding (ws-security) and there is no way to set basicHttpBinding. Everything is behind VPN so I can't offer you link to webservice. Also data is considered secret (request from client) so I can't give you full info, only some "common" things. Here is WS config:
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IServices" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" />
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" proxyCredentialType="None" realm="" />
<message clientCredentialType="UserName" negotiateServiceCredential="true" algorithmSuite="Default" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://topSecert.url/Service.svc"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IServices"
contract="IServices" name="WSHttpBinding_IServices" />
</client>
</system.serviceModel>
My attempts:
1) Basic PHP Soap client does not work. It always hangs until max exec time is reached (no error is generated). I found out later that PHP Soap client does not support wsHttpBinding (wanted to cry)
2) Some SoapClient extension classes but no success, request still hangs.
3) Trying "self-generated" CURL request with SOAPAction header. Finally I got some error there (I generated request with wse class):
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/soap/fault</a:Action>
</s:Header>
<s:Body>
<s:Fault>
<s:Code>
<s:Value>s:Sender</s:Value>
<s:Subcode>
<s:Value xmlns:a="http://schemas.xmlsoap.org/ws/2005/02/sc">a:BadContextToken</s:Value>
</s:Subcode>
</s:Code>
<s:Reason>
<s:Text xml:lang="en-US">The security context token is expired or is
not valid. The message was not processed.</s:Text>
</s:Reason>
</s:Fault>
</s:Body>
I changed my server time to valid zone (same as WCF), tried with nonce, hashed password, plain password and bunch of other things that I can't remember now.
I also tried to compile wso2/wsf however was unable to compile it on PHP 5.4 (I tried to apply provided FIX but it resulted in same error).
Example of test XML:
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns1="https://topSercret.url/Test">
<env:Header>
<wsse:Security
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
env:mustUnderstand="1">
<wsse:UsernameToken>
<wsse:Username><!-- Removed --></wsse:Username>
<wsse:Password
Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"><!-- Removed --></wsse:Password>
<wsse:Nonce><!-- Removed --></wsse:Nonce>
<wsu:Created
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2014-01-19T15:20:31Z</wsu:Created>
</wsse:UsernameToken>
<wsu:Timestamp
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsu:Created>2014-01-19T15:20:31Z</wsu:Created>
<wsu:Expires>2014-01-19T16:20:31Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
</env:Header>
<env:Body>
<ns1:SomeAction />
</env:Body>
And this is code of test script (may have errors, I removed large portion of it for this posting):
<?php
date_default_timezone_set( 'UTC' );
include 'WSSESoap.php';
class TestSoap extends SoapClient {
private $_username;
private $_password;
private $_digest;
// test vars
public $r_request;
public $r_location;
public $r_action;
function addUserToken($username, $password, $digest = false) {
$this->_username = $username;
$this->_password = $password;
$this->_digest = $digest;
}
function __doRequest($request, $location, $saction, $version, $one_way = 0) {
$doc = new DOMDocument('1.0');
$doc->loadXML($request);
$objWSSE = new WSSESoap($doc);
$objWSSE->signAllHeaders = TRUE;
$objWSSE->addTimestamp();
$objWSSE->addUserToken($this->_username, $this->_password, $this->_digest);
// take data for "my" usage
$this->r_request = $objWSSE->saveXML();
$this->r_location = $location;
$this->r_action = $saction;
return '';
}
}
function test()
{
$soapUrl = "https://topSecret.url/Services.svc";
$context = stream_context_create(array(
'ssl' => array(
'verify_peer' => false,
'allow_self_signed' => true
)
));
$client = new TestSoap('/mypath/wsdl.xml', array(
'stream_context' => $context,
'soap_version' => SOAP_1_2,
'trace' => 1,
'connection_timeout' => 10
));
$client->addUserToken('User', 'Password', TRUE );
$requestParams = array(
'data1' => '1',
'data2' => '2',
);
// call to generate request string
$client->myAction($requestParams);
$xml_post_string = $client->r_request;
$headers = array(
"Content-type: application/soap+xml; charset=\"utf-8\"",
"Accept: text/xml,application/soap+xml",
"Cache-Control: no-cache",
"Pragma: no-cache",
"SOAPAction: " . $client->r_action,
"Content-length: " . strlen($xml_post_string)
);
// generate && run cURL request
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_URL, $soapUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml_post_string); // the SOAP request
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
}
test();
So finally the question. Can this kind of service be consumed with PHP (and if it can please help to understand how)?
I solved this some time ago, but never had time to make it more "nicer". So generally problem was the way how wsHttpBinding message security works and how to implement it on PHP. I used concept from https://github.com/enginaygen/kps-soap-client/blob/master/KPSSoapClient.php and also added psha1 from Implementation of P_SHA1 algorithm in PHP.
So the way it needs to work is this:
Here is the imeplentation (NOTE: I have not implemented it by expanding PHP soap client due to problems on WSDL import. Also as I said I used other people concepts and never made to clean up code - especially XML generation).
So to get something like this in request:
Code would be: