File indexing completed on 2024-12-22 05:36:41

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Filter
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version    $Id$
0020  */
0021 
0022 /**
0023  * @see Zend_Filter_Encrypt_Interface
0024  */
0025 // require_once 'Zend/Filter/Encrypt/Interface.php';
0026 
0027 /**
0028  * Encryption adapter for openssl
0029  *
0030  * @category   Zend
0031  * @package    Zend_Filter
0032  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0033  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0034  */
0035 class Zend_Filter_Encrypt_Openssl implements Zend_Filter_Encrypt_Interface
0036 {
0037     /**
0038      * Definitions for encryption
0039      * array(
0040      *     'public'   => public keys
0041      *     'private'  => private keys
0042      *     'envelope' => resulting envelope keys
0043      * )
0044      */
0045     protected $_keys = array(
0046         'public'   => array(),
0047         'private'  => array(),
0048         'envelope' => array()
0049     );
0050 
0051     /**
0052      * Internal passphrase
0053      *
0054      * @var string
0055      */
0056     protected $_passphrase;
0057 
0058     /**
0059      * Internal compression
0060      *
0061      * @var array
0062      */
0063     protected $_compression;
0064 
0065     /**
0066      * Internal create package
0067      *
0068      * @var boolean
0069      */
0070     protected $_package = false;
0071 
0072     /**
0073      * Class constructor
0074      * Available options
0075      *   'public'      => public key
0076      *   'private'     => private key
0077      *   'envelope'    => envelope key
0078      *   'passphrase'  => passphrase
0079      *   'compression' => compress value with this compression adapter
0080      *   'package'     => pack envelope keys into encrypted string, simplifies decryption
0081      *
0082      * @param string|array $options Options for this adapter
0083      */
0084     public function __construct($options = array())
0085     {
0086         if (!extension_loaded('openssl')) {
0087             // require_once 'Zend/Filter/Exception.php';
0088             throw new Zend_Filter_Exception('This filter needs the openssl extension');
0089         }
0090 
0091         if ($options instanceof Zend_Config) {
0092             $options = $options->toArray();
0093         }
0094 
0095         if (!is_array($options)) {
0096             $options = array('public' => $options);
0097         }
0098 
0099         if (array_key_exists('passphrase', $options)) {
0100             $this->setPassphrase($options['passphrase']);
0101             unset($options['passphrase']);
0102         }
0103 
0104         if (array_key_exists('compression', $options)) {
0105             $this->setCompression($options['compression']);
0106             unset($options['compress']);
0107         }
0108 
0109         if (array_key_exists('package', $options)) {
0110             $this->setPackage($options['package']);
0111             unset($options['package']);
0112         }
0113 
0114         $this->_setKeys($options);
0115     }
0116 
0117     /**
0118      * Sets the encryption keys
0119      *
0120      * @param  string|array $keys Key with type association
0121      * @return Zend_Filter_Encrypt_Openssl
0122      */
0123     protected function _setKeys($keys)
0124     {
0125         if (!is_array($keys)) {
0126             // require_once 'Zend/Filter/Exception.php';
0127             throw new Zend_Filter_Exception('Invalid options argument provided to filter');
0128         }
0129 
0130         foreach ($keys as $type => $key) {
0131             if (ctype_print($key) && is_file(realpath($key)) && is_readable($key)) {
0132                 $file = fopen($key, 'r');
0133                 $cert = fread($file, 8192);
0134                 fclose($file);
0135             } else {
0136                 $cert = $key;
0137                 $key  = count($this->_keys[$type]);
0138             }
0139 
0140             switch ($type) {
0141                 case 'public':
0142                     $test = openssl_pkey_get_public($cert);
0143                     if ($test === false) {
0144                         // require_once 'Zend/Filter/Exception.php';
0145                         throw new Zend_Filter_Exception("Public key '{$cert}' not valid");
0146                     }
0147 
0148                     openssl_free_key($test);
0149                     $this->_keys['public'][$key] = $cert;
0150                     break;
0151                 case 'private':
0152                     $test = openssl_pkey_get_private($cert, $this->_passphrase);
0153                     if ($test === false) {
0154                         // require_once 'Zend/Filter/Exception.php';
0155                         throw new Zend_Filter_Exception("Private key '{$cert}' not valid");
0156                     }
0157 
0158                     openssl_free_key($test);
0159                     $this->_keys['private'][$key] = $cert;
0160                     break;
0161                 case 'envelope':
0162                     $this->_keys['envelope'][$key] = $cert;
0163                     break;
0164                 default:
0165                     break;
0166             }
0167         }
0168 
0169         return $this;
0170     }
0171 
0172     /**
0173      * Returns all public keys
0174      *
0175      * @return array
0176      */
0177     public function getPublicKey()
0178     {
0179         $key = $this->_keys['public'];
0180         return $key;
0181     }
0182 
0183     /**
0184      * Sets public keys
0185      *
0186      * @param  string|array $key Public keys
0187      * @return Zend_Filter_Encrypt_Openssl
0188      */
0189     public function setPublicKey($key)
0190     {
0191         if (is_array($key)) {
0192             foreach($key as $type => $option) {
0193                 if ($type !== 'public') {
0194                     $key['public'] = $option;
0195                     unset($key[$type]);
0196                 }
0197             }
0198         } else {
0199             $key = array('public' => $key);
0200         }
0201 
0202         return $this->_setKeys($key);
0203     }
0204 
0205     /**
0206      * Returns all private keys
0207      *
0208      * @return array
0209      */
0210     public function getPrivateKey()
0211     {
0212         $key = $this->_keys['private'];
0213         return $key;
0214     }
0215 
0216     /**
0217      * Sets private keys
0218      *
0219      * @param  string $key Private key
0220      * @param  string $passphrase
0221      * @return Zend_Filter_Encrypt_Openssl
0222      */
0223     public function setPrivateKey($key, $passphrase = null)
0224     {
0225         if (is_array($key)) {
0226             foreach($key as $type => $option) {
0227                 if ($type !== 'private') {
0228                     $key['private'] = $option;
0229                     unset($key[$type]);
0230                 }
0231             }
0232         } else {
0233             $key = array('private' => $key);
0234         }
0235 
0236         if ($passphrase !== null) {
0237             $this->setPassphrase($passphrase);
0238         }
0239 
0240         return $this->_setKeys($key);
0241     }
0242 
0243     /**
0244      * Returns all envelope keys
0245      *
0246      * @return array
0247      */
0248     public function getEnvelopeKey()
0249     {
0250         $key = $this->_keys['envelope'];
0251         return $key;
0252     }
0253 
0254     /**
0255      * Sets envelope keys
0256      *
0257      * @param  string|array $options Envelope keys
0258      * @return Zend_Filter_Encrypt_Openssl
0259      */
0260     public function setEnvelopeKey($key)
0261     {
0262         if (is_array($key)) {
0263             foreach($key as $type => $option) {
0264                 if ($type !== 'envelope') {
0265                     $key['envelope'] = $option;
0266                     unset($key[$type]);
0267                 }
0268             }
0269         } else {
0270             $key = array('envelope' => $key);
0271         }
0272 
0273         return $this->_setKeys($key);
0274     }
0275 
0276     /**
0277      * Returns the passphrase
0278      *
0279      * @return string
0280      */
0281     public function getPassphrase()
0282     {
0283         return $this->_passphrase;
0284     }
0285 
0286     /**
0287      * Sets a new passphrase
0288      *
0289      * @param string $passphrase
0290      * @return Zend_Filter_Encrypt_Openssl
0291      */
0292     public function setPassphrase($passphrase)
0293     {
0294         $this->_passphrase = $passphrase;
0295         return $this;
0296     }
0297 
0298     /**
0299      * Returns the compression
0300      *
0301      * @return array
0302      */
0303     public function getCompression()
0304     {
0305         return $this->_compression;
0306     }
0307 
0308     /**
0309      * Sets a internal compression for values to encrypt
0310      *
0311      * @param string|array $compression
0312      * @return Zend_Filter_Encrypt_Openssl
0313      */
0314     public function setCompression($compression)
0315     {
0316         if (is_string($this->_compression)) {
0317             $compression = array('adapter' => $compression);
0318         }
0319 
0320         $this->_compression = $compression;
0321         return $this;
0322     }
0323 
0324     /**
0325      * Returns if header should be packaged
0326      *
0327      * @return boolean
0328      */
0329     public function getPackage()
0330     {
0331         return $this->_package;
0332     }
0333 
0334     /**
0335      * Sets if the envelope keys should be included in the encrypted value
0336      *
0337      * @param boolean $package
0338      * @return Zend_Filter_Encrypt_Openssl
0339      */
0340     public function setPackage($package)
0341     {
0342         $this->_package = (boolean) $package;
0343         return $this;
0344     }
0345 
0346     /**
0347      * Encrypts $value with the defined settings
0348      * Note that you also need the "encrypted" keys to be able to decrypt
0349      *
0350      * @param  string $value Content to encrypt
0351      * @return string The encrypted content
0352      * @throws Zend_Filter_Exception
0353      */
0354     public function encrypt($value)
0355     {
0356         $encrypted     = array();
0357         $encryptedkeys = array();
0358 
0359         if (count($this->_keys['public']) == 0) {
0360             // require_once 'Zend/Filter/Exception.php';
0361             throw new Zend_Filter_Exception('Openssl can not encrypt without public keys');
0362         }
0363 
0364         $keys         = array();
0365         $fingerprints = array();
0366         $count        = -1;
0367         foreach($this->_keys['public'] as $key => $cert) {
0368             $keys[$key] = openssl_pkey_get_public($cert);
0369             if ($this->_package) {
0370                 $details = openssl_pkey_get_details($keys[$key]);
0371                 if ($details === false) {
0372                     $details = array('key' => 'ZendFramework');
0373                 }
0374 
0375                 ++$count;
0376                 $fingerprints[$count] = md5($details['key']);
0377             }
0378         }
0379 
0380         // compress prior to encryption
0381         if (!empty($this->_compression)) {
0382             // require_once 'Zend/Filter/Compress.php';
0383             $compress = new Zend_Filter_Compress($this->_compression);
0384             $value    = $compress->filter($value);
0385         }
0386 
0387         $crypt  = openssl_seal($value, $encrypted, $encryptedkeys, $keys);
0388         foreach ($keys as $key) {
0389             openssl_free_key($key);
0390         }
0391 
0392         if ($crypt === false) {
0393             // require_once 'Zend/Filter/Exception.php';
0394             throw new Zend_Filter_Exception('Openssl was not able to encrypt your content with the given options');
0395         }
0396 
0397         $this->_keys['envelope'] = $encryptedkeys;
0398 
0399         // Pack data and envelope keys into single string
0400         if ($this->_package) {
0401             $header = pack('n', count($this->_keys['envelope']));
0402             foreach($this->_keys['envelope'] as $key => $envKey) {
0403                 $header .= pack('H32n', $fingerprints[$key], strlen($envKey)) . $envKey;
0404             }
0405 
0406             $encrypted = $header . $encrypted;
0407         }
0408 
0409         return $encrypted;
0410     }
0411 
0412     /**
0413      * Defined by Zend_Filter_Interface
0414      *
0415      * Decrypts $value with the defined settings
0416      *
0417      * @param  string $value Content to decrypt
0418      * @return string The decrypted content
0419      * @throws Zend_Filter_Exception
0420      */
0421     public function decrypt($value)
0422     {
0423         $decrypted = "";
0424         $envelope  = current($this->getEnvelopeKey());
0425 
0426         if (count($this->_keys['private']) !== 1) {
0427             // require_once 'Zend/Filter/Exception.php';
0428             throw new Zend_Filter_Exception('Please give a private key for decryption with Openssl');
0429         }
0430 
0431         if (!$this->_package && empty($envelope)) {
0432             // require_once 'Zend/Filter/Exception.php';
0433             throw new Zend_Filter_Exception('Please give a envelope key for decryption with Openssl');
0434         }
0435 
0436         foreach($this->_keys['private'] as $key => $cert) {
0437             $keys = openssl_pkey_get_private($cert, $this->getPassphrase());
0438         }
0439 
0440         if ($this->_package) {
0441             $details = openssl_pkey_get_details($keys);
0442             if ($details !== false) {
0443                 $fingerprint = md5($details['key']);
0444             } else {
0445                 $fingerprint = md5("ZendFramework");
0446             }
0447 
0448             $count = unpack('ncount', $value);
0449             $count = $count['count'];
0450             $length  = 2;
0451             for($i = $count; $i > 0; --$i) {
0452                 $header = unpack('H32print/nsize', substr($value, $length, 18));
0453                 $length  += 18;
0454                 if ($header['print'] == $fingerprint) {
0455                     $envelope = substr($value, $length, $header['size']);
0456                 }
0457 
0458                 $length += $header['size'];
0459             }
0460 
0461             // remainder of string is the value to decrypt
0462             $value = substr($value, $length);
0463         }
0464 
0465         $crypt  = openssl_open($value, $decrypted, $envelope, $keys);
0466         openssl_free_key($keys);
0467 
0468         if ($crypt === false) {
0469             // require_once 'Zend/Filter/Exception.php';
0470             throw new Zend_Filter_Exception('Openssl was not able to decrypt you content with the given options');
0471         }
0472 
0473         // decompress after decryption
0474         if (!empty($this->_compression)) {
0475             // require_once 'Zend/Filter/Decompress.php';
0476             $decompress = new Zend_Filter_Decompress($this->_compression);
0477             $decrypted  = $decompress->filter($decrypted);
0478         }
0479 
0480         return $decrypted;
0481     }
0482 
0483     /**
0484      * Returns the adapter name
0485      *
0486      * @return string
0487      */
0488     public function toString()
0489     {
0490         return 'Openssl';
0491     }
0492 }