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