PageRenderTime 86ms CodeModel.GetById 40ms app.highlight 14ms RepoModel.GetById 26ms app.codeStats 0ms

/library/Zend/InfoCard.php

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