PHP Soap client with WCF BadContextToken

2019-04-15 04:20发布

问题:

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)?

回答1:

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:

  1. PHP request for security token from WSS with its request secret
  2. WS generates security token and returns it to PHP
  3. PHP generates SOAP C14N signed message with requested security token

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).

// TODO implement this by extending SoapClient class
// currently not implemented in it because request params are not generated correctly

/**
 * Client implementing SOAP wsHttpBinding with message security. <br>
 * NOTE: this is adapted to work for special needs of our client. It can be modified and there is a lot of work that jet needs to be done (nicer code, options and optimization).
 */

class WSSoap
{
    /**
     * Securit token request template
     */
    const STS_TEMPLATE = <<<X
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><s:Header><a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT</a:Action><a:MessageID></a:MessageID><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand="1"></a:To><o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><u:Timestamp u:Id="_0"><u:Created></u:Created><u:Expires></u:Expires></u:Timestamp><o:UsernameToken u:Id="_1"><o:Username></o:Username><o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"></o:Password></o:UsernameToken></o:Security></s:Header><s:Body><t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust"><t:TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</t:TokenType><t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType><t:Entropy><t:BinarySecret Type="http://schemas.xmlsoap.org/ws/2005/02/trust/Nonce"></t:BinarySecret></t:Entropy><t:KeySize>256</t:KeySize></t:RequestSecurityToken></s:Body></s:Envelope>
X;

    /**
     * Any action request template (mainly for headers)
     */
    const KPS_TEMPLATE = <<<X
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><s:Header><a:Action s:mustUnderstand="1">n</a:Action><a:MessageID></a:MessageID><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand="1"></a:To><o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><u:Timestamp u:Id="_0"><u:Created></u:Created><u:Expires></u:Expires></u:Timestamp><c:SecurityContextToken xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc"><c:Identifier></c:Identifier></c:SecurityContextToken><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"></SignatureMethod><Reference URI="#_0"> <Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod><DigestValue></DigestValue></Reference></SignedInfo><SignatureValue></SignatureValue><KeyInfo><o:SecurityTokenReference><o:Reference ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct"></o:Reference></o:SecurityTokenReference></KeyInfo></Signature></o:Security></s:Header><s:Body></s:Body></s:Envelope>
X;

    /**
     * Namespaces
     */
    const S11 = "http://schemas.xmlsoap.org/soap/envelope/";
    const S12 = "http://www.w3.org/2003/05/soap-envelope";
    const WSU = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
    const WSSE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
    const WSSE11 = "http://docs.oasis-open.org/wss/oasis-wss-wsecurity-secext-1.1.xsd";
    const WST = "http://schemas.xmlsoap.org/ws/2005/02/trust";
    const DS = "http://www.w3.org/2000/09/xmldsig#";
    const XENC = "http://www.w3.org/2001/04/xmlenc#";
    const WSP = "http://schemas.xmlsoap.org/ws/2004/09/policy";
    const WSA = "http://www.w3.org/2005/08/addressing";
    const XS = "http://www.w3.org/2001/XMLSchema";
    const WSDL = "http://schemas.xmlsoap.org/wsdl/";
    const SP = "http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702";
    const SC = "http://schemas.xmlsoap.org/ws/2005/02/sc";

    /**
     * STS Properties
     */
    protected $stsHostName;
    protected $stsEndpoint;
    protected $stsUsername;
    protected $stsPassword;
    protected $stsNamespace;

    /**
     * Binary secret used for generating request
     */
    protected $requestSecret;
    protected $rstrBinarySecret;
    protected $rstrKeyIdentifier;

    protected $token;
    protected $tokenReference;

    function __construct( $username, $password, $endpointURL, $namespace )
    {
        $this->stsUsername = $username;
        $this->stsPassword = $password;
        $this->stsHostName = parse_url( $endpointURL, PHP_URL_HOST);
        $this->stsEndpoint = $endpointURL;
        $this->stsNamespace = $namespace;
    }



    function request( $action, $fullActionName, $params )
    {
        $this->stsRequest();

        $kpsDom = new \DOMDocument("1.0", "utf-8");
        $kpsDom->preserveWhiteSpace = false;
        $kpsDom->loadXML(static::KPS_TEMPLATE);

        $kpsXpath = new \DOMXPath($kpsDom);
        $kpsXpath->registerNamespace('S12', static::S12);
        $kpsXpath->registerNamespace('WSA', static::WSA);
        $kpsXpath->registerNamespace('WSU', static::WSU);
        $kpsXpath->registerNamespace('WSSE', static::WSSE);
        $kpsXpath->registerNamespace('XENC', static::XENC);
        $kpsXpath->registerNamespace('DS', static::DS);
        $kpsXpath->registerNamespace('SC', static::SC);

        // Addressing

        $uuid = $this->uuid();

        $actionPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSA:Action");
        $messageIDPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSA:MessageID");
        $toPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSA:To");

        $actionPath->item(0)->nodeValue = $fullActionName;
        $messageIDPath->item(0)->nodeValue = sprintf("urn:uuid:%s", $uuid);
        $toPath->item(0)->nodeValue = $this->stsEndpoint;

        // Timestamp

        $time = time();

        $dateCreated = gmdate('Y-m-d\TH:i:s\Z', $time);
        $dateExpires = gmdate('Y-m-d\TH:i:s\Z', $time + (5 * 60));

        $timestampPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSU:Timestamp");
        $timestampDateCreatedPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSU:Timestamp/WSU:Created");
        $timestampDateExpiresPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSU:Timestamp/WSU:Expires");
        $timestampDateCreatedPath->item(0)->nodeValue = $dateCreated;
        $timestampDateExpiresPath->item(0)->nodeValue = $dateExpires;
        $timestampC14N = $timestampPath->item(0)->C14N(true, false);

        // DigestValue
        $digestValue = base64_encode(hash('sha1', $timestampC14N, true));
        $digestValuePath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/DS:Signature/DS:SignedInfo/DS:Reference/DS:DigestValue");
        $digestValuePath->item(0)->nodeValue = $digestValue;

        // Signature
        $signaturePath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/DS:Signature/DS:SignedInfo");
        $signatureValuePath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/DS:Signature/DS:SignatureValue");
        $signatureC14N = $signaturePath->item(0)->C14N(true, false);

        $psBinary = $this->psha1( $this->requestSecret, $this->rstrBinarySecret );
        $signatureValue = base64_encode(hash_hmac("sha1", $signatureC14N, $psBinary, true));
        $signatureValuePath->item(0)->nodeValue = $signatureValue;

        // token reference
        $securityContextTokenReference = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/DS:Signature/DS:KeyInfo/WSSE:SecurityTokenReference/WSSE:Reference");
        $securityContextTokenReference->item(0)->setAttribute('URI', "#$this->tokenReference");
        // token ID
        $tokenPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/SC:SecurityContextToken");
        $tokenPath->item(0)->setAttribute('u:Id', $this->tokenReference);
        // token
        $tokenPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/SC:SecurityContextToken/SC:Identifier");
        $tokenPath->item(0)->nodeValue = $this->token;

        // Message
        $bodyElemet = $kpsXpath->query("//S12:Envelope/S12:Body")->item(0);
        $root = $kpsDom->createElementNS( $this->stsNamespace, $action );

        foreach( $params as $name => $value ) {
            $root->appendChild( $kpsDom->createElement( $name, $value ) );
        }

        $bodyElemet->appendChild( $root );
        $kpsRequest = $kpsDom->saveXML();

        // Request
        try {
            $stsResponse = $this->execCurl( $kpsRequest );
        } catch ( \Exception $e ) {
            throw $e;
        }

        return $stsResponse;
    }


    /**
     * Performs a STS request
     *
     * @param string $location Request location
     */
    protected function stsRequest()
    {
        $rstXml = static::STS_TEMPLATE;

        $rstDom = new \DOMDocument("1.0", "utf-8");
        $rstDom->preserveWhiteSpace = false;
        $rstDom->loadXML($rstXml);

        $rstXpath = new \DOMXPath($rstDom);
        $rstXpath->registerNamespace('S12', static::S12);
        $rstXpath->registerNamespace('WSA', static::WSA);
        $rstXpath->registerNamespace('WSU', static::WSU);
        $rstXpath->registerNamespace('WSSE', static::WSSE);
        $rstXpath->registerNamespace('XENC', static::XENC);
        $rstXpath->registerNamespace('DS', static::DS);
        $rstXpath->registerNamespace('WST', static::WST);
        $rstXpath->registerNamespace('WSP', static::WSP);

        // Addressing

        $uuid = $this->uuid();

        $messageIDPath = $rstXpath->query("//S12:Envelope/S12:Header/WSA:MessageID");
        $toPath = $rstXpath->query("//S12:Envelope/S12:Header/WSA:To");

        $messageIDPath->item(0)->nodeValue = sprintf("urn:uuid:%s", $uuid);
        $toPath->item(0)->nodeValue = $this->stsEndpoint;

        // Timestamp

        $time = time();

        $dateCreated = gmdate('Y-m-d\TH:i:s\Z', $time);
        $dateExpires = gmdate('Y-m-d\TH:i:s\Z', $time + (5 * 60));

        $timestampDateCreatedPath = $rstXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSU:Timestamp/WSU:Created");
        $timestampDateExpiresPath = $rstXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSU:Timestamp/WSU:Expires");
        $timestampDateCreatedPath->item(0)->nodeValue = $dateCreated;
        $timestampDateExpiresPath->item(0)->nodeValue = $dateExpires;

        // Credentials

        $usernamePath = $rstXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSSE:UsernameToken/WSSE:Username");
        $passwordPath = $rstXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSSE:UsernameToken/WSSE:Password");

        $usernamePath->item(0)->nodeValue = $this->stsUsername;
        $passwordPath->item(0)->nodeValue = $this->stsPassword;

        // Set binary key
        $this->requestSecret = uniqid();
        $binaryKeyPath = $rstXpath->query("//S12:Envelope/S12:Body/WST:RequestSecurityToken/WST:Entropy/WST:BinarySecret");
        $binaryKeyPath->item(0)->nodeValue = base64_encode( $this->requestSecret );

        // Endpoint
        $stsRequest = $rstDom->saveXML();

        // Request
        try {
            $stsResponse = $this->execCurl( $stsRequest );
        } catch ( \Exception $e ) {
            throw $e;
        }

        $rstrDom = new \DOMDocument("1.0", "utf-8");
        $rstrDom->preserveWhiteSpace = false;
        $rstrDom->loadXML($stsResponse);

        $rstrXpath = new \DOMXPath($rstrDom);

        $rstrXpath->registerNamespace('S12', static::S12);
        $rstrXpath->registerNamespace('WSA', static::WSA);
        $rstrXpath->registerNamespace('WSU', static::WSU);
        $rstrXpath->registerNamespace('WSSE', static::WSSE);
        $rstrXpath->registerNamespace('XENC', static::XENC);
        $rstrXpath->registerNamespace('DS', static::DS);
        $rstrXpath->registerNamespace('WST', static::WST);
        $rstrXpath->registerNamespace('WSP', static::WSP);
        $rstrXpath->registerNamespace('SC', static::SC);

        // parse security context token
        $securityContextTokenReference = $rstrXpath->query("//S12:Envelope/S12:Body/WST:RequestSecurityTokenResponse/WST:RequestedSecurityToken/SC:SecurityContextToken");
        $this->tokenReference = $securityContextTokenReference->item(0)->getAttribute('u:Id');

        $securityContextToken = $rstrXpath->query("//S12:Envelope/S12:Body/WST:RequestSecurityTokenResponse/WST:RequestedSecurityToken/SC:SecurityContextToken/SC:Identifier");
        $this->token = $securityContextToken->item(0)->nodeValue;

        $securityContextToken = $rstrXpath->query("//S12:Envelope/S12:Body/WST:RequestSecurityTokenResponse/WST:Entropy/WST:BinarySecret");

        $this->rstrBinarySecret = base64_decode( $securityContextToken->item(0)->nodeValue );
    }

    protected function execCurl( $request )
    {
        // Request
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $this->stsEndpoint);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // disable SSL verification - re-enable if needed
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            "Host: " . $this->stsHostName,
            "Content-Type: application/soap+xml; charset=utf-8",
            "Content-Length: " . strlen( $request ),
        ));
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $request );
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $response = curl_exec($ch);

        if ( $response === false ) {
            throw new \Exception(curl_error($ch));
        }

        curl_close($ch);

        return $response;
    }

    /**
     * Generates UUID
     *
     * @return string UUID
     */
    protected function uuid()
    {
        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', //
            mt_rand(0, 0xffff), //
            mt_rand(0, 0xffff), //
            mt_rand(0, 0xffff), //
            mt_rand(0, 0x0fff) | 0x4000, //
            mt_rand(0, 0x3fff) | 0x8000, //
            mt_rand(0, 0xffff), //
            mt_rand(0, 0xffff), //
            mt_rand(0, 0xffff) //
        );
    }


    /**
     * Calculate psha1 hash used for signature generation
     * @param unknown $clientSecret
     * @param unknown $serverSecret
     * @param number $sizeBits
     * @return string
     */
    protected function psha1($clientSecret, $serverSecret, $sizeBits = 256)
    {
        $sizeBytes = $sizeBits / 8;

        $hmacKey = $clientSecret;
        $hashSize = 160; // HMAC_SHA1 length is always 160
        $bufferSize = $hashSize / 8 + strlen($serverSecret);
        $i = 0;

        $b1 = $serverSecret;
        $b2 = "";
        $temp = null;
        $psha = array();

        while ($i < $sizeBytes) {
            $b1 = hash_hmac('SHA1', $b1, $hmacKey, true);
            $b2 = $b1 . $serverSecret;
            $temp = hash_hmac('SHA1', $b2, $hmacKey, true);

            for ($j = 0; $j < strlen($temp); $j++) {
                if ($i < $sizeBytes) {
                    $psha[$i] = $temp[$j];
                    $i++;
                } else {
                    break;
                }
            }
        }

        return implode("", $psha);
    }
}

So to get something like this in request:

<s:Header>
<a:Action s:mustUnderstand="1">https://some.url/NamespaceName/IServices/CheckTransaction</a:Action>
...
</s:Header>
<s:Body>
    <CheckTransaction xmlns="https://sime.url/ActionToDo">
        <TransactionID>1234567</TransactionID>
    </CheckTransaction>
</s:Body>

Code would be:

$url = 'https://some.url/Services.svc';
$namespace = 'https://some.url/NamespaceName'; // this is action namespace you need, since there is no WSDL parsing you need to set it by yourself

try {
    $c = new WSSoap( $username, $password, $url, $namespace );
    $params = array(
        'TransactionID' => '1234567'
    );
    $r = $c->request( 'CheckTransaction', 'https://some.url/NamespaceName/IServices/CheckTransaction', $params ); // also applies - no WSDL parsing so we need to set params
} catch (Exception $e) {
    throw $e;
}