PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/InfoCard.php

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