PageRenderTime 40ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/BeSimple/SoapServer/WsSecurityFilter.php

https://bitbucket.org/wizzmedia/nexity-generateur
PHP | 240 lines | 144 code | 28 blank | 68 comment | 24 complexity | e44fcc53572a9d154ddf36f92e1dfe86 MD5 | raw file
Possible License(s): Apache-2.0
  1. <?php
  2. /*
  3. * This file is part of the BeSimpleSoapServer.
  4. *
  5. * (c) Christian Kerl <christian-kerl@web.de>
  6. * (c) Francis Besset <francis.besset@gmail.com>
  7. *
  8. * This source file is subject to the MIT license that is bundled
  9. * with this source code in the file LICENSE.
  10. */
  11. namespace BeSimple\SoapServer;
  12. use ass\XmlSecurity\DSig as XmlSecurityDSig;
  13. use ass\XmlSecurity\Enc as XmlSecurityEnc;
  14. use BeSimple\SoapCommon\FilterHelper;
  15. use BeSimple\SoapCommon\Helper;
  16. use BeSimple\SoapCommon\SoapRequest as CommonSoapRequest;
  17. use BeSimple\SoapCommon\SoapRequestFilter;
  18. use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse;
  19. use BeSimple\SoapCommon\SoapResponseFilter;
  20. use BeSimple\SoapCommon\WsSecurityFilterClientServer;
  21. /**
  22. * This plugin implements a subset of the following standards:
  23. * * Web Services Security: SOAP Message Security 1.0 (WS-Security 2004)
  24. * http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0.pdf
  25. * * Web Services Security UsernameToken Profile 1.0
  26. * http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0.pdf
  27. * * Web Services Security X.509 Certificate Token Profile
  28. * http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0.pdf
  29. *
  30. * @author Andreas Schamberger <mail@andreass.net>
  31. */
  32. class WsSecurityFilter extends WsSecurityFilterClientServer implements SoapRequestFilter, SoapResponseFilter
  33. {
  34. /**
  35. * Username/password callback that returns password or null.
  36. *
  37. * @var callable
  38. */
  39. protected $usernamePasswordCallback;
  40. /**
  41. * Set username/password callback that returns password or null.
  42. *
  43. * @param callable $callback Username/password callback function
  44. *
  45. * @return void
  46. */
  47. public function setUsernamePasswordCallback($callback)
  48. {
  49. $this->usernamePasswordCallback = $callback;
  50. }
  51. /**
  52. * Reset all properties to default values.
  53. */
  54. public function resetFilter()
  55. {
  56. parent::resetFilter();
  57. $this->usernamePasswordCallback = null;
  58. }
  59. /**
  60. * Modify the given request XML.
  61. *
  62. * @param \BeSimple\SoapCommon\SoapRequest $request SOAP request
  63. *
  64. * @return void
  65. */
  66. public function filterRequest(CommonSoapRequest $request)
  67. {
  68. // get \DOMDocument from SOAP request
  69. $dom = $request->getContentDocument();
  70. // locate security header
  71. $security = $dom->getElementsByTagNameNS(Helper::NS_WSS, 'Security')->item(0);
  72. if (null !== $security) {
  73. // is security header still valid?
  74. $query = '//'.Helper::PFX_WSU.':Timestamp/'.Helper::PFX_WSU.':Expires';
  75. $xpath = new \DOMXPath($dom);
  76. $xpath->registerNamespace(Helper::PFX_WSU, Helper::NS_WSU);
  77. $expires = $xpath->query($query, $security)->item(0);
  78. if (null !== $expires) {
  79. $expiresDatetime = \DateTime::createFromFormat(self::DATETIME_FORMAT, $expires->textContent, new \DateTimeZone('UTC'));
  80. $currentDatetime = new \DateTime('now', new \DateTimeZone('UTC'));
  81. if ($currentDatetime > $expiresDatetime) {
  82. throw new \SoapFault('wsu:MessageExpired', 'Security semantics are expired');
  83. }
  84. }
  85. $usernameToken = $security->getElementsByTagNameNS(Helper::NS_WSS, 'UsernameToken')->item(0);
  86. if (null !== $usernameToken) {
  87. $usernameTokenUsername = $usernameToken->getElementsByTagNameNS(Helper::NS_WSS, 'Username')->item(0);
  88. $usernameTokenPassword = $usernameToken->getElementsByTagNameNS(Helper::NS_WSS, 'Password')->item(0);
  89. $password = call_user_func($this->usernamePasswordCallback, $usernameTokenUsername->textContent);
  90. if ($usernameTokenPassword->getAttribute('Type') == Helper::NAME_WSS_UTP . '#PasswordDigest') {
  91. $nonce = $usernameToken->getElementsByTagNameNS(Helper::NS_WSS, 'Nonce')->item(0);
  92. $created = $usernameToken->getElementsByTagNameNS(Helper::NS_WSU, 'Created')->item(0);
  93. $password = base64_encode(sha1(base64_decode($nonce->textContent) . $created->textContent . $password, true));
  94. }
  95. if (null === $password || $usernameTokenPassword->textContent != $password) {
  96. throw new \SoapFault('wsse:FailedAuthentication', 'The security token could not be authenticated or authorized');
  97. }
  98. }
  99. // add SecurityTokenReference resolver for KeyInfo
  100. $keyResolver = array($this, 'keyInfoSecurityTokenReferenceResolver');
  101. XmlSecurityDSig::addKeyInfoResolver(Helper::NS_WSS, 'SecurityTokenReference', $keyResolver);
  102. // do we have a reference list in header
  103. $referenceList = XmlSecurityEnc::locateReferenceList($security);
  104. // get a list of encrypted nodes
  105. $encryptedNodes = XmlSecurityEnc::locateEncryptedData($dom, $referenceList);
  106. // decrypt them
  107. if (null !== $encryptedNodes) {
  108. foreach ($encryptedNodes as $encryptedNode) {
  109. XmlSecurityEnc::decryptNode($encryptedNode);
  110. }
  111. }
  112. // locate signature node
  113. $signature = XmlSecurityDSig::locateSignature($security);
  114. if (null !== $signature) {
  115. // verify references
  116. $options = array(
  117. 'id_ns_prefix' => Helper::PFX_WSU,
  118. 'id_prefix_ns' => Helper::NS_WSU,
  119. );
  120. if (XmlSecurityDSig::verifyReferences($signature, $options) !== true) {
  121. throw new \SoapFault('wsse:FailedCheck', 'The signature or decryption was invalid');
  122. }
  123. // verify signature
  124. if (XmlSecurityDSig::verifyDocumentSignature($signature) !== true) {
  125. throw new \SoapFault('wsse:FailedCheck', 'The signature or decryption was invalid');
  126. }
  127. }
  128. $security->parentNode->removeChild($security);
  129. }
  130. }
  131. /**
  132. * Modify the given request XML.
  133. *
  134. * @param \BeSimple\SoapCommon\SoapResponse $response SOAP response
  135. *
  136. * @return void
  137. */
  138. public function filterResponse(CommonSoapResponse $response)
  139. {
  140. // get \DOMDocument from SOAP response
  141. $dom = $response->getContentDocument();
  142. // create FilterHelper
  143. $filterHelper = new FilterHelper($dom);
  144. // add the neccessary namespaces
  145. $filterHelper->addNamespace(Helper::PFX_WSS, Helper::NS_WSS);
  146. $filterHelper->addNamespace(Helper::PFX_WSU, Helper::NS_WSU);
  147. $filterHelper->registerNamespace(XmlSecurityDSig::PFX_XMLDSIG, XmlSecurityDSig::NS_XMLDSIG);
  148. // init timestamp
  149. $dt = new \DateTime('now', new \DateTimeZone('UTC'));
  150. $createdTimestamp = $dt->format(self::DATETIME_FORMAT);
  151. // create security header
  152. $security = $filterHelper->createElement(Helper::NS_WSS, 'Security');
  153. $filterHelper->addHeaderElement($security, true, $this->actor, $response->getVersion());
  154. if (true === $this->addTimestamp || null !== $this->expires) {
  155. $timestamp = $filterHelper->createElement(Helper::NS_WSU, 'Timestamp');
  156. $created = $filterHelper->createElement(Helper::NS_WSU, 'Created', $createdTimestamp);
  157. $timestamp->appendChild($created);
  158. if (null !== $this->expires) {
  159. $dt->modify('+' . $this->expires . ' seconds');
  160. $expiresTimestamp = $dt->format(self::DATETIME_FORMAT);
  161. $expires = $filterHelper->createElement(Helper::NS_WSU, 'Expires', $expiresTimestamp);
  162. $timestamp->appendChild($expires);
  163. }
  164. $security->appendChild($timestamp);
  165. }
  166. if (null !== $this->userSecurityKey && $this->userSecurityKey->hasKeys()) {
  167. $guid = 'CertId-' . Helper::generateUUID();
  168. // add token references
  169. $keyInfo = null;
  170. if (null !== $this->tokenReferenceSignature) {
  171. $keyInfo = $this->createKeyInfo($filterHelper, $this->tokenReferenceSignature, $guid, $this->userSecurityKey->getPublicKey());
  172. }
  173. $nodes = $this->createNodeListForSigning($dom, $security);
  174. $signature = XmlSecurityDSig::createSignature($this->userSecurityKey->getPrivateKey(), XmlSecurityDSig::EXC_C14N, $security, null, $keyInfo);
  175. $options = array(
  176. 'id_ns_prefix' => Helper::PFX_WSU,
  177. 'id_prefix_ns' => Helper::NS_WSU,
  178. );
  179. foreach ($nodes as $node) {
  180. XmlSecurityDSig::addNodeToSignature($signature, $node, XmlSecurityDSig::SHA1, XmlSecurityDSig::EXC_C14N, $options);
  181. }
  182. XmlSecurityDSig::signDocument($signature, $this->userSecurityKey->getPrivateKey(), XmlSecurityDSig::EXC_C14N);
  183. $publicCertificate = $this->userSecurityKey->getPublicKey()->getX509Certificate(true);
  184. $binarySecurityToken = $filterHelper->createElement(Helper::NS_WSS, 'BinarySecurityToken', $publicCertificate);
  185. $filterHelper->setAttribute($binarySecurityToken, null, 'EncodingType', Helper::NAME_WSS_SMS . '#Base64Binary');
  186. $filterHelper->setAttribute($binarySecurityToken, null, 'ValueType', Helper::NAME_WSS_X509 . '#X509v3');
  187. $filterHelper->setAttribute($binarySecurityToken, Helper::NS_WSU, 'Id', $guid);
  188. $security->insertBefore($binarySecurityToken, $signature);
  189. // encrypt soap document
  190. if (null !== $this->serviceSecurityKey && $this->serviceSecurityKey->hasKeys()) {
  191. $guid = 'EncKey-' . Helper::generateUUID();
  192. // add token references
  193. $keyInfo = null;
  194. if (null !== $this->tokenReferenceEncryption) {
  195. $keyInfo = $this->createKeyInfo($filterHelper, $this->tokenReferenceEncryption, $guid, $this->serviceSecurityKey->getPublicKey());
  196. }
  197. $encryptedKey = XmlSecurityEnc::createEncryptedKey($guid, $this->serviceSecurityKey->getPrivateKey(), $this->serviceSecurityKey->getPublicKey(), $security, $signature, $keyInfo);
  198. $referenceList = XmlSecurityEnc::createReferenceList($encryptedKey);
  199. // token reference to encrypted key
  200. $keyInfo = $this->createKeyInfo($filterHelper, self::TOKEN_REFERENCE_SECURITY_TOKEN, $guid);
  201. $nodes = $this->createNodeListForEncryption($dom);
  202. foreach ($nodes as $node) {
  203. $type = XmlSecurityEnc::ELEMENT;
  204. if ($node->localName == 'Body') {
  205. $type = XmlSecurityEnc::CONTENT;
  206. }
  207. XmlSecurityEnc::encryptNode($node, $type, $this->serviceSecurityKey->getPrivateKey(), $referenceList, $keyInfo);
  208. }
  209. }
  210. }
  211. }
  212. }