PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/library/Zend/InfoCard.php

https://bitbucket.org/baruffaldi/website-2008-computer-shopping-3
PHP | 488 lines | 232 code | 84 blank | 172 comment | 31 complexity | 44f06beaeaa3bd425674ddc4d4869185 MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_InfoCard
  17. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. * @version $Id: InfoCard.php 11010 2008-08-24 19:22:58Z thomas $
  20. */
  21. /**
  22. * Zend_InfoCard_Xml_EncryptedData
  23. */
  24. require_once 'Zend/InfoCard/Xml/EncryptedData.php';
  25. /**
  26. * Zend_InfoCard_Xml_Assertion
  27. */
  28. require_once 'Zend/InfoCard/Xml/Assertion.php';
  29. /**
  30. * Zend_InfoCard_Exception
  31. */
  32. require_once 'Zend/InfoCard/Exception.php';
  33. /**
  34. * Zend_InfoCard_Cipher
  35. */
  36. require_once 'Zend/InfoCard/Cipher.php';
  37. /**
  38. * Zend_InfoCard_Xml_Security
  39. */
  40. require_once 'Zend/InfoCard/Xml/Security.php';
  41. /**
  42. * Zend_InfoCard_Adapter_Interface
  43. */
  44. require_once 'Zend/InfoCard/Adapter/Interface.php';
  45. /**
  46. * Zend_InfoCard_Claims
  47. */
  48. require_once 'Zend/InfoCard/Claims.php';
  49. /**
  50. * @category Zend
  51. * @package Zend_InfoCard
  52. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  53. * @license http://framework.zend.com/license/new-bsd New BSD License
  54. */
  55. class Zend_InfoCard
  56. {
  57. /**
  58. * URI for XML Digital Signature SHA1 Digests
  59. */
  60. const DIGEST_SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1';
  61. /**
  62. * An array of certificate pair files and optional passwords for them to search
  63. * when trying to determine which certificate was used to encrypt the transient key
  64. *
  65. * @var Array
  66. */
  67. protected $_keyPairs;
  68. /**
  69. * The instance to use to decrypt public-key encrypted data
  70. *
  71. * @var Zend_InfoCard_Cipher_Pki_Interface
  72. */
  73. protected $_pkiCipherObj;
  74. /**
  75. * The instance to use to decrypt symmetric encrypted data
  76. *
  77. * @var Zend_InfoCard_Cipher_Symmetric_Interface
  78. */
  79. protected $_symCipherObj;
  80. /**
  81. * The InfoCard Adapter to use for callbacks into the application using the component
  82. * such as when storing assertions, etc.
  83. *
  84. * @var Zend_InfoCard_Adapter_Interface
  85. */
  86. protected $_adapter;
  87. /**
  88. * InfoCard Constructor
  89. *
  90. * @throws Zend_InfoCard_Exception
  91. */
  92. public function __construct()
  93. {
  94. $this->_keyPairs = array();
  95. if(!extension_loaded('mcrypt')) {
  96. throw new Zend_InfoCard_Exception("Use of the Zend_InfoCard component requires the mcrypt extension to be enabled in PHP");
  97. }
  98. if(!extension_loaded('openssl')) {
  99. throw new Zend_InfoCard_Exception("Use of the Zend_InfoCard component requires the openssl extension to be enabled in PHP");
  100. }
  101. }
  102. /**
  103. * Sets the adapter uesd for callbacks into the application using the component, used
  104. * when doing things such as storing / retrieving assertions, etc.
  105. *
  106. * @param Zend_InfoCard_Adapter_Interface $a The Adapter instance
  107. * @return Zend_InfoCard The instnace
  108. */
  109. public function setAdapter(Zend_InfoCard_Adapter_Interface $a)
  110. {
  111. $this->_adapter = $a;
  112. return $this;
  113. }
  114. /**
  115. * Retrieves the adapter used for callbacks into the application using the component.
  116. * If no adapter was set then an instance of Zend_InfoCard_Adapter_Default is used
  117. *
  118. * @return Zend_InfoCard_Adapter_Interface The Adapter instance
  119. */
  120. public function getAdapter()
  121. {
  122. if(is_null($this->_adapter)) {
  123. require_once 'Zend/InfoCard/Adapter/Default.php';
  124. $this->setAdapter(new Zend_InfoCard_Adapter_Default());
  125. }
  126. return $this->_adapter;
  127. }
  128. /**
  129. * Gets the Public Key Cipher object used in this instance
  130. *
  131. * @return Zend_InfoCard_Cipher_Pki_Interface
  132. */
  133. public function getPkiCipherObject()
  134. {
  135. return $this->_pkiCipherObj;
  136. }
  137. /**
  138. * Sets the Public Key Cipher Object used in this instance
  139. *
  140. * @param Zend_InfoCard_Cipher_Pki_Interface $cipherObj
  141. * @return Zend_InfoCard
  142. */
  143. public function setPkiCipherObject(Zend_InfoCard_Cipher_Pki_Interface $cipherObj)
  144. {
  145. $this->_pkiCipherObj = $cipherObj;
  146. return $this;
  147. }
  148. /**
  149. * Get the Symmetric Cipher Object used in this instance
  150. *
  151. * @return Zend_InfoCard_Cipher_Symmetric_Interface
  152. */
  153. public function getSymCipherObject()
  154. {
  155. return $this->_symCipherObj;
  156. }
  157. /**
  158. * Sets the Symmetric Cipher Object used in this instance
  159. *
  160. * @param Zend_InfoCard_Cipher_Symmetric_Interface $cipherObj
  161. * @return Zend_InfoCard
  162. */
  163. public function setSymCipherObject($cipherObj)
  164. {
  165. $this->_symCipherObj = $cipherObj;
  166. return $this;
  167. }
  168. /**
  169. * Remove a Certificate Pair by Key ID from the search list
  170. *
  171. * @throws Zend_InfoCard_Exception
  172. * @param string $key_id The Certificate Key ID returned from adding the certificate pair
  173. * @return Zend_InfoCard
  174. */
  175. public function removeCertificatePair($key_id)
  176. {
  177. if(!key_exists($key_id, $this->_keyPairs)) {
  178. throw new Zend_InfoCard_Exception("Attempted to remove unknown key id: $key_id");
  179. }
  180. unset($this->_keyPairs[$key_id]);
  181. return $this;
  182. }
  183. /**
  184. * Add a Certificate Pair to the list of certificates searched by the component
  185. *
  186. * @throws Zend_InfoCard_Exception
  187. * @param string $private_key_file The path to the private key file for the pair
  188. * @param string $public_key_file The path to the certificate / public key for the pair
  189. * @param string $type (optional) The URI for the type of key pair this is (default RSA with OAEP padding)
  190. * @param string $password (optional) The password for the private key file if necessary
  191. * @return string A key ID representing this key pair in the component
  192. */
  193. public function addCertificatePair($private_key_file, $public_key_file, $type = Zend_InfoCard_Cipher::ENC_RSA_OAEP_MGF1P, $password = null)
  194. {
  195. if(!file_exists($private_key_file) ||
  196. !file_exists($public_key_file)) {
  197. throw new Zend_InfoCard_Exception("Could not locate the public and private certificate pair files: $private_key_file, $public_key_file");
  198. }
  199. if(!is_readable($private_key_file) ||
  200. !is_readable($public_key_file)) {
  201. throw new Zend_InfoCard_Exception("Could not read the public and private certificate pair files (check permissions): $private_key_file, $public_key_file");
  202. }
  203. $key_id = md5($private_key_file.$public_key_file);
  204. if(key_exists($key_id, $this->_keyPairs)) {
  205. throw new Zend_InfoCard_Exception("Attempted to add previously existing certificate pair: $private_key_file, $public_key_file");
  206. }
  207. switch($type) {
  208. case Zend_InfoCard_Cipher::ENC_RSA:
  209. case Zend_InfoCard_Cipher::ENC_RSA_OAEP_MGF1P:
  210. $this->_keyPairs[$key_id] = array('private' => $private_key_file,
  211. 'public' => $public_key_file,
  212. 'type_uri' => $type);
  213. if(!is_null($password)) {
  214. $this->_keyPairs[$key_id]['password'] = $password;
  215. } else {
  216. $this->_keyPairs[$key_id]['password'] = null;
  217. }
  218. return $key_id;
  219. break;
  220. default:
  221. throw new Zend_InfoCard_Exception("Invalid Certificate Pair Type specified: $type");
  222. }
  223. }
  224. /**
  225. * Return a Certificate Pair from a key ID
  226. *
  227. * @throws Zend_InfoCard_Exception
  228. * @param string $key_id The Key ID of the certificate pair in the component
  229. * @return array An array containing the path to the private/public key files,
  230. * the type URI and the password if provided
  231. */
  232. public function getCertificatePair($key_id)
  233. {
  234. if(key_exists($key_id, $this->_keyPairs)) {
  235. return $this->_keyPairs[$key_id];
  236. }
  237. throw new Zend_InfoCard_Exception("Invalid Certificate Pair ID provided: $key_id");
  238. }
  239. /**
  240. * Retrieve the digest of a given public key / certificate using the provided digest
  241. * method
  242. *
  243. * @throws Zend_InfoCard_Exception
  244. * @param string $key_id The certificate key id in the component
  245. * @param string $digestMethod The URI of the digest method to use (default SHA1)
  246. * @return string The digest value in binary format
  247. */
  248. protected function _getPublicKeyDigest($key_id, $digestMethod = self::DIGEST_SHA1)
  249. {
  250. $certificatePair = $this->getCertificatePair($key_id);
  251. $temp = file($certificatePair['public']);
  252. unset($temp[count($temp)-1]);
  253. unset($temp[0]);
  254. $certificateData = base64_decode(implode("\n", $temp));
  255. switch($digestMethod) {
  256. case self::DIGEST_SHA1:
  257. $digest_retval = sha1($certificateData, true);
  258. break;
  259. default:
  260. throw new Zend_InfoCard_Exception("Invalid Digest Type Provided: $digestMethod");
  261. }
  262. return $digest_retval;
  263. }
  264. /**
  265. * Find a certificate pair based on a digest of its public key / certificate file
  266. *
  267. * @param string $digest The digest value of the public key wanted in binary form
  268. * @param string $digestMethod The URI of the digest method used to calculate the digest
  269. * @return mixed The Key ID of the matching certificate pair or false if not found
  270. */
  271. protected function _findCertifiatePairByDigest($digest, $digestMethod = self::DIGEST_SHA1)
  272. {
  273. foreach($this->_keyPairs as $key_id => $certificate_data) {
  274. $cert_digest = $this->_getPublicKeyDigest($key_id, $digestMethod);
  275. if($cert_digest == $digest) {
  276. return $key_id;
  277. }
  278. }
  279. return false;
  280. }
  281. /**
  282. * Extracts the Signed Token from an EncryptedData block
  283. *
  284. * @throws Zend_InfoCard_Exception
  285. * @param string $strXmlToken The EncryptedData XML block
  286. * @return string The XML of the Signed Token inside of the EncryptedData block
  287. */
  288. protected function _extractSignedToken($strXmlToken)
  289. {
  290. $encryptedData = Zend_InfoCard_Xml_EncryptedData::getInstance($strXmlToken);
  291. // Determine the Encryption Method used to encrypt the token
  292. switch($encryptedData->getEncryptionMethod()) {
  293. case Zend_InfoCard_Cipher::ENC_AES128CBC:
  294. case Zend_InfoCard_Cipher::ENC_AES256CBC:
  295. break;
  296. default:
  297. throw new Zend_InfoCard_Exception("Unknown Encryption Method used in the secure token");
  298. }
  299. // Figure out the Key we are using to decrypt the token
  300. $keyinfo = $encryptedData->getKeyInfo();
  301. if(!($keyinfo instanceof Zend_InfoCard_Xml_KeyInfo_XmlDSig)) {
  302. throw new Zend_InfoCard_Exception("Expected a XML digital signature KeyInfo, but was not found");
  303. }
  304. $encryptedKey = $keyinfo->getEncryptedKey();
  305. switch($encryptedKey->getEncryptionMethod()) {
  306. case Zend_InfoCard_Cipher::ENC_RSA:
  307. case Zend_InfoCard_Cipher::ENC_RSA_OAEP_MGF1P:
  308. break;
  309. default:
  310. throw new Zend_InfoCard_Exception("Unknown Key Encryption Method used in secure token");
  311. }
  312. $securityTokenRef = $encryptedKey->getKeyInfo()->getSecurityTokenReference();
  313. $key_id = $this->_findCertifiatePairByDigest($securityTokenRef->getKeyReference());
  314. if(!$key_id) {
  315. throw new Zend_InfoCard_Exception("Unable to find key pair used to encrypt symmetric InfoCard Key");
  316. }
  317. $certificate_pair = $this->getCertificatePair($key_id);
  318. // Santity Check
  319. if($certificate_pair['type_uri'] != $encryptedKey->getEncryptionMethod()) {
  320. throw new Zend_InfoCard_Exception("Certificate Pair which matches digest is not of same algorithm type as document, check addCertificate()");
  321. }
  322. $PKcipher = Zend_InfoCard_Cipher::getInstanceByURI($encryptedKey->getEncryptionMethod());
  323. $base64DecodeSupportsStrictParam = version_compare(PHP_VERSION, '5.2.0', '>=');
  324. if ($base64DecodeSupportsStrictParam) {
  325. $keyCipherValueBase64Decoded = base64_decode($encryptedKey->getCipherValue(), true);
  326. } else {
  327. $keyCipherValueBase64Decoded = base64_decode($encryptedKey->getCipherValue());
  328. }
  329. $symmetricKey = $PKcipher->decrypt(
  330. $keyCipherValueBase64Decoded,
  331. file_get_contents($certificate_pair['private']),
  332. $certificate_pair['password']
  333. );
  334. $symCipher = Zend_InfoCard_Cipher::getInstanceByURI($encryptedData->getEncryptionMethod());
  335. if ($base64DecodeSupportsStrictParam) {
  336. $dataCipherValueBase64Decoded = base64_decode($encryptedData->getCipherValue(), true);
  337. } else {
  338. $dataCipherValueBase64Decoded = base64_decode($encryptedData->getCipherValue());
  339. }
  340. $signedToken = $symCipher->decrypt($dataCipherValueBase64Decoded, $symmetricKey);
  341. return $signedToken;
  342. }
  343. /**
  344. * Process an input Infomation Card EncryptedData block sent from the client,
  345. * validate it, and return the claims contained within it on success or an error message on error
  346. *
  347. * @param string $strXmlToken The XML token sent to the server from the client
  348. * @return Zend_Infocard_Claims The Claims object containing the claims, or any errors which occurred
  349. */
  350. public function process($strXmlToken)
  351. {
  352. $retval = new Zend_InfoCard_Claims();
  353. try {
  354. $signedAssertionsXml = $this->_extractSignedToken($strXmlToken);
  355. } catch(Zend_InfoCard_Exception $e) {
  356. $retval->setError('Failed to extract assertion document');
  357. $retval->setCode(Zend_InfoCard_Claims::RESULT_PROCESSING_FAILURE);
  358. return $retval;
  359. }
  360. try {
  361. $assertions = Zend_InfoCard_Xml_Assertion::getInstance($signedAssertionsXml);
  362. } catch(Zend_InfoCard_Exception $e) {
  363. $retval->setError('Failure processing assertion document');
  364. $retval->setCode(Zend_InfoCard_Claims::RESULT_PROCESSING_FAILURE);
  365. return $retval;
  366. }
  367. if(!($assertions instanceof Zend_InfoCard_Xml_Assertion_Interface)) {
  368. throw new Zend_InfoCard_Exception("Invalid Assertion Object returned");
  369. }
  370. if(!($reference_id = Zend_InfoCard_Xml_Security::validateXMLSignature($assertions->asXML()))) {
  371. $retval->setError("Failure Validating the Signature of the assertion document");
  372. $retval->setCode(Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE);
  373. return $retval;
  374. }
  375. // The reference id should be locally scoped as far as I know
  376. if($reference_id[0] == '#') {
  377. $reference_id = substr($reference_id, 1);
  378. } else {
  379. $retval->setError("Reference of document signature does not reference the local document");
  380. $retval->setCode(Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE);
  381. return $retval;
  382. }
  383. // Make sure the signature is in reference to the same document as the assertions
  384. if($reference_id != $assertions->getAssertionID()) {
  385. $retval->setError("Reference of document signature does not reference the local document");
  386. $retval->setCode(Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE);
  387. }
  388. // Validate we haven't seen this before and the conditions are acceptable
  389. $conditions = $this->getAdapter()->retrieveAssertion($assertions->getAssertionURI(), $assertions->getAssertionID());
  390. if($conditions === false) {
  391. $conditions = $assertions->getConditions();
  392. }
  393. if(is_array($condition_error = $assertions->validateConditions($conditions))) {
  394. $retval->setError("Conditions of assertion document are not met: {$condition_error[1]} ({$condition_error[0]})");
  395. $retval->setCode(Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE);
  396. }
  397. $attributes = $assertions->getAttributes();
  398. $retval->setClaims($attributes);
  399. if($retval->getCode() == 0) {
  400. $retval->setCode(Zend_InfoCard_Claims::RESULT_SUCCESS);
  401. }
  402. return $retval;
  403. }
  404. }