<?php 
/**
 * soap-wsse.php
 *
 * Copyright (c) 2007, Robert Richards <rrichards@ctindustries.net>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *
 *   * Neither the name of Robert Richards nor the names of his
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @author     Robert Richards <rrichards@ctindustries.net>
 * @copyright  2007 Robert Richards <rrichards@ctindustries.net>
 * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
 * @version    1.0.0
 */
 
require('xmlseclibs.php');

class 
WSSESoap {
    const 
WSSENS 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
    const 
WSUNS 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';
    const 
WSUNAME 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0';
    const 
WSSEPFX 'wsse';
    const 
WSUPFX 'wsu';
    private 
$soapNS$soapPFX;
    private 
$soapDoc NULL;
    private 
$envelope NULL;
    private 
$SOAPXPath NULL;
    private 
$secNode NULL;
    public 
$signAllHeaders FALSE;
    
    private function 
locateSecurityHeader($bMustUnderstand TRUE$setActor NULL) {
        if (
$this->secNode == NULL) {
            
$headers $this->SOAPXPath->query('//wssoap:Envelope/wssoap:Header');
            
$header $headers->item(0);
            if (! 
$header) {
                
$header $this->soapDoc->createElementNS($this->soapNS$this->soapPFX.':Header');
                
$this->envelope->insertBefore($header$this->envelope->firstChild);
            }
            
$secnodes $this->SOAPXPath->query('./wswsse:Security'$header);
            
$secnode NULL;
            foreach (
$secnodes AS $node) {
                
$actor $node->getAttributeNS($this->soapNS'actor');
                if (
$actor == $setActor) {
                    
$secnode $node;
                    break;
                }
            }
            if (! 
$secnode) {
                
$secnode $this->soapDoc->createElementNS(WSSESoap::WSSENSWSSESoap::WSSEPFX.':Security');
                
$header->appendChild($secnode);
                if (
$bMustUnderstand) {
                    
$secnode->setAttributeNS($this->soapNS$this->soapPFX.':mustUnderstand''1');
                }
                if (! empty(
$setActor)) {
                    
$ename 'actor';
                    if (
$this->soapNS == 'http://www.w3.org/2003/05/soap-envelope') {
                        
$ename 'role';
                    }
                    
$secnode->setAttributeNS($this->soapNS$this->soapPFX.':'.$ename$setActor);
                }
            }
            
$this->secNode $secnode;
        }
        return 
$this->secNode;
    }

    public function 
__construct($doc$bMustUnderstand TRUE$setActor=NULL) {
        
$this->soapDoc $doc;
        
$this->envelope $doc->documentElement;
        
$this->soapNS $this->envelope->namespaceURI;
        
$this->soapPFX $this->envelope->prefix;
        
$this->SOAPXPath = new DOMXPath($doc);
        
$this->SOAPXPath->registerNamespace('wssoap'$this->soapNS);
        
$this->SOAPXPath->registerNamespace('wswsse'WSSESoap::WSSENS);
        
$this->locateSecurityHeader($bMustUnderstand$setActor);
    }

    public function 
addTimestamp($secondsToExpire=3600) {
        
/* Add the WSU timestamps */
        
$security $this->locateSecurityHeader();

        
$timestamp $this->soapDoc->createElementNS(WSSESoap::WSUNSWSSESoap::WSUPFX.':Timestamp');
        
$security->insertBefore($timestamp$security->firstChild);
        
$currentTime time();
        
$created $this->soapDoc->createElementNS(WSSESoap::WSUNS,  WSSESoap::WSUPFX.':Created'gmdate("Y-m-d\TH:i:s"$currentTime).'Z');
        
$timestamp->appendChild($created);
        if (! 
is_null($secondsToExpire)) {
            
$expire $this->soapDoc->createElementNS(WSSESoap::WSUNS,  WSSESoap::WSUPFX.':Expires'gmdate("Y-m-d\TH:i:s"$currentTime $secondsToExpire).'Z');
            
$timestamp->appendChild($expire);
        }
    }

    public function 
addUserToken($userName$password=NULL$passwordDigest=FALSE) {
        if (
$passwordDigest && empty($password)) {
            throw new 
Exception("Cannot calculate the digest without a password");
        }
        
        
$security $this->locateSecurityHeader();

        
$token $this->soapDoc->createElementNS(WSSESoap::WSSENSWSSESoap::WSSEPFX.':UsernameToken');
        
$security->insertBefore($token$security->firstChild);

        
$username $this->soapDoc->createElementNS(WSSESoap::WSSENS,  WSSESoap::WSSEPFX.':Username'$userName);
        
$token->appendChild($username);
        
        
/* Generate nonce - create a 256 bit session key to be used */
        
$objKey = new XMLSecurityKey(XMLSecurityKey::AES256_CBC);
        
$nonce $objKey->generateSessionKey();
        unset(
$objKey);
        
$createdate gmdate("Y-m-d\TH:i:s").'Z';
        
        if (
$password) {
            
$passType WSSESoap::WSUNAME.'#PasswordText';
            if (
$passwordDigest) {
                
$password base64_encode(sha1($nonce.$createdate$passwordtrue));
                
$passType WSSESoap::WSUNAME.'#PasswordDigest';
            }
            
$passwordNode $this->soapDoc->createElementNS(WSSESoap::WSSENS,  WSSESoap::WSSEPFX.':Password'$password);
            
$token->appendChild($passwordNode);
            
$passwordNode->setAttribute('Type'$passType);
        }

        
$nonceNode $this->soapDoc->createElementNS(WSSESoap::WSSENS,  WSSESoap::WSSEPFX.':Nonce'base64_encode($nonce));
        
$token->appendChild($nonceNode);

        
$created $this->soapDoc->createElementNS(WSSESoap::WSUNS,  WSSESoap::WSUPFX.':Created'$createdate);
        
$token->appendChild($created);
    }

    public function 
addBinaryToken($cert$isPEMFormat=TRUE$isDSig=TRUE) {
        
$security $this->locateSecurityHeader();
        
$data XMLSecurityDSig::get509XCert($cert$isPEMFormat);

        
$token $this->soapDoc->createElementNS(WSSESoap::WSSENSWSSESoap::WSSEPFX.':BinarySecurityToken'$data);
        
$security->insertBefore($token$security->firstChild);

        
$token->setAttribute('EncodingType''http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary');
        
$token->setAttributeNS(WSSESoap::WSUNSWSSESoap::WSUPFX.':Id'XMLSecurityDSig::generate_GUID());
        
$token->setAttribute('ValueType''http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3');
        
        return 
$token;
    }
    
    public function 
attachTokentoSig($token) {
        if (! (
$token instanceof DOMElement)) {
            throw new 
Exception('Invalid parameter: BinarySecurityToken element expected');
        }
        
$objXMLSecDSig = new XMLSecurityDSig();
        if (
$objDSig $objXMLSecDSig->locateSignature($this->soapDoc)) {
            
$tokenURI '#'.$token->getAttributeNS(WSSESoap::WSUNS"Id");
            
$this->SOAPXPath->registerNamespace('secdsig'XMLSecurityDSig::XMLDSIGNS);
            
$query "./secdsig:KeyInfo";
            
$nodeset $this->SOAPXPath->query($query$objDSig);
            
$keyInfo $nodeset->item(0);
            if (! 
$keyInfo) {
                
$keyInfo $objXMLSecDSig->createNewSignNode('KeyInfo');
                
$objDSig->appendChild($keyInfo);
            }
            
            
$tokenRef $this->soapDoc->createElementNS(WSSESoap::WSSENSWSSESoap::WSSEPFX.':SecurityTokenReference');
            
$keyInfo->appendChild($tokenRef);
            
$reference $this->soapDoc->createElementNS(WSSESoap::WSSENSWSSESoap::WSSEPFX.':Reference');
            
$reference->setAttribute("URI"$tokenURI);
            
$tokenRef->appendChild($reference);
        } else {
            throw new 
Exception('Unable to locate digital signature');
        }
    }

    public function 
signSoapDoc($objKey) {
        
$objDSig = new XMLSecurityDSig();

        
$objDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);

        
$arNodes = array();
        foreach (
$this->secNode->childNodes AS $node) {
            if (
$node->nodeType == XML_ELEMENT_NODE) {
                
$arNodes[] = $node;
            }
        }

        if (
$this->signAllHeaders) {
            foreach (
$this->secNode->parentNode->childNodes AS $node) {
                if ((
$node->nodeType == XML_ELEMENT_NODE) && 
                (
$node->namespaceURI != WSSESoap::WSSENS)) {
                    
$arNodes[] = $node;
                }
            }
        }

        foreach (
$this->envelope->childNodes AS $node) {
            if (
$node->namespaceURI == $this->soapNS && $node->localName == 'Body') {
                
$arNodes[] = $node;
                break;
            }
        }
        
        
$arOptions = array('prefix'=>WSSESoap::WSUPFX'prefix_ns'=>WSSESoap::WSUNS);
        
$objDSig->addReferenceList($arNodesXMLSecurityDSig::SHA1NULL$arOptions);

        
$objDSig->sign($objKey);

        
$objDSig->appendSignature($this->secNodeTRUE);
    }

    public function 
addEncryptedKey($node$key$token) {
        if (! 
$key->encKey) {
            return 
FALSE;
        }
        
$encKey $key->encKey;
        
$security $this->locateSecurityHeader();
        
$doc $security->ownerDocument;
        if (! 
$doc->isSameNode($encKey->ownerDocument)) {
            
$key->encKey $security->ownerDocument->importNode($encKeyTRUE);
            
$encKey $key->encKey;
        }
        if (! empty(
$key->guid)) {
            return 
TRUE;
        }
        
        
$lastToken NULL;
        
$findTokens $security->firstChild;
        while (
$findTokens) {
            if (
$findTokens->localName == 'BinarySecurityToken') {
                
$lastToken $findTokens;
            }
            
$findTokens $findTokens->nextSibling;
        }
        if (
$lastToken) {
            
$lastToken $lastToken->nextSibling;
        }

        
$security->insertBefore($encKey$lastToken);
        
$key->guid XMLSecurityDSig::generate_GUID();
        
$encKey->setAttribute('Id'$key->guid);
        
$encMethod $encKey->firstChild;
        while (
$encMethod && $encMethod->localName != 'EncryptionMethod') {
            
$encMethod $encMethod->nextChild;
        }
        if (
$encMethod) {
            
$encMethod $encMethod->nextSibling;
        }
        
$objDoc $encKey->ownerDocument;
        
$keyInfo $objDoc->createElementNS('http://www.w3.org/2000/09/xmldsig#''dsig:KeyInfo');
        
$encKey->insertBefore($keyInfo$encMethod);
        
$tokenURI '#'.$token->getAttributeNS(WSSESoap::WSUNS"Id");
        
$tokenRef $objDoc->createElementNS(WSSESoap::WSSENSWSSESoap::WSSEPFX.':SecurityTokenReference');
        
$keyInfo->appendChild($tokenRef);
        
$reference $objDoc->createElementNS(WSSESoap::WSSENSWSSESoap::WSSEPFX.':Reference');
        
$reference->setAttribute("URI"$tokenURI);
        
$tokenRef->appendChild($reference);

        return 
TRUE;
    }

    public function 
AddReference($baseNode$guid) {
        
$refList NULL;
        
$child $baseNode->firstChild;
        while(
$child) {
            if ((
$child->namespaceURI == XMLSecEnc::XMLENCNS) && ($child->localName == 'ReferenceList')) {
                
$refList $child;
                break;
            }
            
$child $child->nextSibling;
        }
        
$doc $baseNode->ownerDocument;
        if (
is_null($refList)) {
            
$refList $doc->createElementNS(XMLSecEnc::XMLENCNS'xenc:ReferenceList');
            
$baseNode->appendChild($refList);
        }
        
$dataref $doc->createElementNS(XMLSecEnc::XMLENCNS'xenc:DataReference');
        
$refList->appendChild($dataref);
        
$dataref->setAttribute('URI''#'.$guid);
    }

    public function 
EncryptBody($siteKey$objKey$token) {

        
$enc = new XMLSecEnc();
        foreach (
$this->envelope->childNodes AS $node) {
            if (
$node->namespaceURI == $this->soapNS && $node->localName == 'Body') {
                break;
            }
        }
        
$enc->setNode($node);
        
/* encrypt the symmetric key */
        
$enc->encryptKey($siteKey$objKeyFALSE);

        
$enc->type XMLSecEnc::Content;
        
/* Using the symmetric key to actually encrypt the data */
        
$encNode $enc->encryptNode($objKey);

        
$guid XMLSecurityDSig::generate_GUID();
        
$encNode->setAttribute('Id'$guid);

        
$refNode $encNode->firstChild;
        while(
$refNode && $refNode->nodeType != XML_ELEMENT_NODE) {
            
$refNode $refNode->nextSibling;
        }
        if (
$refNode) {
            
$refNode $refNode->nextSibling;
        }
        if (
$this->addEncryptedKey($encNode$enc$token)) {
            
$this->AddReference($enc->encKey$guid);
        }
    }
    
    public function 
saveXML() {
        return 
$this->soapDoc->saveXML();
    }

    public function 
save($file) {
        return 
$this->soapDoc->save($file);
    }
}

?>