diff options
Diffstat (limited to 'system')
-rw-r--r-- | system/libraries/Encryption.php | 816 | ||||
-rw-r--r-- | system/libraries/Session/drivers/Session_cookie.php | 55 |
2 files changed, 846 insertions, 25 deletions
diff --git a/system/libraries/Encryption.php b/system/libraries/Encryption.php new file mode 100644 index 000000000..503fb64e3 --- /dev/null +++ b/system/libraries/Encryption.php @@ -0,0 +1,816 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP 5.2.4 or newer + * + * NOTICE OF LICENSE + * + * Licensed under the Open Software License version 3.0 + * + * This source file is subject to the Open Software License (OSL 3.0) that is + * bundled with this package in the files license.txt / license.rst. It is + * also available through the world wide web at this URL: + * http://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to obtain it + * through the world wide web, please send an email to + * licensing@ellislab.com so we can send you a copy immediately. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) + * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * @link http://codeigniter.com + * @since Version 3.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * CodeIgniter Encryption Class + * + * Provides two-way keyed encryption via PHP's MCrypt and/or OpenSSL extensions. + * + * @package CodeIgniter + * @subpackage Libraries + * @category Libraries + * @author Andrey Andreev + * @link http://codeigniter.com/user_guide/libraries/encryption.html + */ +class CI_Encryption { + + /** + * Encryption cipher + * + * @var string + */ + protected $_cipher = 'rijndael-128'; + + /** + * Cipher mode + * + * @var string + */ + protected $_mode = 'cbc'; + + /** + * Cipher handle + * + * @var mixed + */ + protected $_handle; + + /** + * Encryption key + * + * @var string + */ + protected $_key; + + /** + * PHP extension to be used + * + * @var string + */ + protected $_driver; + + /** + * List of usable drivers (PHP extensions) + * + * @var array + */ + protected $_drivers = array(); + + /** + * List of supported HMAC algorightms + * + * name => digest size pairs + * + * @var array + */ + protected $_digests = array( + 'sha224' => 28, + 'sha256' => 32, + 'sha384' => 48, + 'sha512' => 64 + ); + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * @param array $params Configuration parameters + * @return void + */ + public function __construct(array $params = array()) + { + $this->_drivers = array( + 'mcrypt' => extension_loaded('mcrypt'), + // While OpenSSL is available for PHP 5.3.0, an IV parameter + // for the encrypt/decrypt functions is only available since 5.3.3 + 'openssl' => (is_php('5.3.3') && extension_loaded('openssl')) + ); + + if ( ! $this->_drivers['mcrypt'] && ! $this->_drivers['openssl']) + { + return show_error('Encryption: Unable to find an available encryption driver.'); + } + // Our configuration validates against the existence of MCRYPT_MODE_* constants, + // but MCrypt supports CTR mode without actually having a constant for it, so ... + elseif ($this->_drivers['mcrypt'] && ! defined('MCRYPT_MODE_CTR')) + { + define('MCRYPT_MODE_CTR', 'ctr'); + } + + $this->initialize($params); + + isset($this->_key) OR $this->_key = config_item('encryption_key'); + if (empty($this->_key)) + { + return show_error('Encryption: You are required to set an encryption key in your configuration.'); + } + + log_message('debug', 'Encryption Class Initialized'); + } + + // -------------------------------------------------------------------- + + /** + * Initialize + * + * @param array $params Configuration parameters + * @return CI_Encryption + */ + public function initialize(array $params) + { + if ( ! empty($params['driver'])) + { + if (isset($this->_drivers[$params['driver']])) + { + if ($this->_drivers[$params['driver']]) + { + $this->_driver = $params['driver']; + } + else + { + log_message('error', "Encryption: Driver '".$params['driver']."' is not available."); + } + } + else + { + log_message('error', "Encryption: Unknown driver '".$params['driver']."' cannot be configured."); + } + } + + if (empty($this->_driver)) + { + $this->_driver = ($this->_drivers['mcrypt'] === TRUE) + ? 'mcrypt' + : 'openssl'; + + log_message('debug', "Encryption: Auto-configured driver '".$this->_driver."'."); + } + + empty($params['key']) OR $this->_key = $params['key']; + $this->{'_'.$this->_driver.'_initialize'}($params); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Initialize MCrypt + * + * @param array $params Configuration parameters + * @return void + */ + protected function _mcrypt_initialize($params) + { + if ( ! empty($params['cipher'])) + { + $params['cipher'] = strtolower($params['cipher']); + $this->_cipher_alias($params['cipher']); + + if ( ! in_array($params['cipher'], mcrypt_list_algorithms(), TRUE)) + { + log_message('error', 'Encryption: MCrypt cipher '.strtoupper($params['cipher']).' is not available.'); + } + else + { + $this->_cipher = $params['cipher']; + } + } + + if ( ! empty($params['mode'])) + { + if ( ! defined('MCRYPT_MODE_'.$params['mode'])) + { + log_message('error', 'Encryption: MCrypt mode '.strtotupper($params['mode']).' is not available.'); + } + else + { + $this->_mode = constant('MCRYPT_MODE_'.$params['mode']); + } + } + + if (isset($this->_cipher, $this->_mode)) + { + if (is_resource($this->_handle) + && (strtolower(mcrypt_enc_get_algorithms_name($this->_handle)) !== $this->_cipher + OR strtolower(mcrypt_enc_get_modes_name($this->_handle)) !== $this->_mode) + ) + { + mcrypt_module_close($this->_handle); + } + + if ($this->_handle = mcrypt_module_open($this->_cipher, '', $this->_mode, '')) + { + log_message('debug', 'Encryption: MCrypt cipher '.strtoupper($this->_cipher).' initialized in '.strtoupper($this->_mode).' mode.'); + } + else + { + log_message('error', 'Encryption: Unable to initialize MCrypt with cipher '.strtoupper($this->_cipher).' in '.strtoupper($this->_mode).' mode.'); + } + } + } + + // -------------------------------------------------------------------- + + /** + * Initialize OpenSSL + * + * @param array $params Configuration parameters + * @return void + */ + protected function _openssl_initialize($params) + { + if ( ! empty($params['cipher'])) + { + $params['cipher'] = strtolower($params['cipher']); + $this->_cipher_alias($params['cipher']); + $this->_cipher = $params['cipher']; + } + + if ( ! empty($params['mode'])) + { + $this->_mode = strtolower($params['mode']); + } + + if (isset($this->_cipher, $this->_mode)) + { + // OpenSSL methods aren't suffixed with '-stream' for this mode + $handle = ($this->_mode === 'stream') + ? $this->_cipher + : $this->_cipher.'-'.$this->_mode; + + if ( ! in_array($handle, openssl_get_cipher_methods(), TRUE)) + { + $this->_handle = NULL; + log_message('error', 'Encryption: Unable to initialize OpenSSL with method '.strtoupper($handle).'.'); + } + else + { + $this->_handle = $handle; + log_message('debug', 'Encryption: OpenSSL initialized with method '.strtoupper($handle).'.'); + } + } + } + + // -------------------------------------------------------------------- + + /** + * Encrypt + * + * @param string $data Input data + * @param array $params Input parameters + * @return string + */ + public function encrypt($data, array $params = NULL) + { + if (($params = $this->_get_params($params)) === FALSE) + { + return FALSE; + } + elseif ( ! isset($params['iv'])) + { + $params['iv'] = ($iv_size = $this->{'_'.$this->_driver.'_get_iv_size'}($params['handle'])) + ? $this->{'_'.$this->_driver.'_get_iv'}($iv_size) + : NULL; + } + + if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE) + { + return FALSE; + } + + if ($params['base64']) + { + $data = base64_encode($data); + } + + if ($params['hmac'] !== FALSE) + { + if ( ! isset($params['hmac']['key'])) + { + $params['hmac']['key'] = $this->hkdf( + $params['key'], + $params['hmac']['digest'], + NULL, + NULL, + 'authentication' + ); + } + + return hash_hmac($params['hmac']['digest'], $data, $params['hmac']['key'], ! $params['base64']).$data; + } + + return $data; + } + + // -------------------------------------------------------------------- + + /** + * Encrypt via MCrypt + * + * @param string $data Input data + * @param array $params Input parameters + * @return string + */ + protected function _mcrypt_encrypt($data, $params) + { + if ( ! is_resource($params['handle'])) + { + return FALSE; + } + elseif (mcrypt_generic_init($params['handle'], $params['key'], $params['iv']) < 0) + { + if ($params['handle'] !== $this->_handle) + { + mcrypt_module_close($params['handle']); + } + + return FALSE; + } + + // Use PKCS#7 padding in order to ensure compatibility with OpenSSL + // and other implementations outside of PHP + $block_size = mcrypt_enc_get_block_size($params['handle']); + $pad = $block_size - (strlen($data) % $block_size); + $data .= str_repeat(chr($pad), $pad); + + $data = $params['iv'].mcrypt_generic($params['handle'], $data); + + mcrypt_generic_deinit($params['handle']); + if ($params['handle'] !== $this->_handle) + { + mcrypt_module_close($params['handle']); + } + + return $data; + } + + // -------------------------------------------------------------------- + + /** + * Encrypt via OpenSSL + * + * @param string $data Input data + * @param array $params Input parameters + * @return string + */ + protected function _openssl_encrypt($data, $params) + { + if (empty($params['handle'])) + { + return FALSE; + } + + $data = openssl_encrypt( + $data, + $params['handle'], + $params['key'], + 1, // DO NOT TOUCH! + $params['iv'] + ); + + if ($data === FALSE) + { + return FALSE; + } + + return $params['iv'].$data; + } + + // -------------------------------------------------------------------- + + /** + * Decrypt + * + * @param string $data Encrypted data + * @param array $params Input parameters + * @return string + */ + public function decrypt($data, array $params = NULL) + { + if (($params = $this->_get_params($params)) === FALSE) + { + return FALSE; + } + + if ($params['hmac'] !== FALSE) + { + if ( ! isset($params['hmac']['key'])) + { + $params['hmac']['key'] = $this->hkdf( + $params['key'], + $params['hmac']['digest'], + NULL, + NULL, + 'authentication' + ); + } + + // This might look illogical, but it is done during encryption as well ... + // The 'base64' value is effectively an inverted "raw data" parameter + $digest_size = ($params['base64']) + ? $this->_digests[$params['hmac']['digest']] * 2 + : $this->_digests[$params['hmac']['digest']]; + $hmac = substr($data, 0, $digest_size); + $data = substr($data, $digest_size); + + if ($hmac !== hash_hmac($params['hmac']['digest'], $data, $params['hmac']['key'], ! $params['base64'])) + { + return FALSE; + } + } + + if ($params['base64']) + { + $data = base64_decode($data); + } + + if ( ! isset($params['iv'])) + { + $iv_size = $this->{'_'.$this->_driver.'_get_iv_size'}($params['handle']); + if ($iv_size = $this->{'_'.$this->_driver.'_get_iv_size'}($params['handle'])) + { + $params['iv'] = substr($data, 0, $iv_size); + $data = substr($data, $iv_size); + } + else + { + $params['iv'] = NULL; + } + } + elseif (strncmp($params['iv'], $data, $iv_size = strlen($params['iv'])) === 0) + { + $data = substr($data, $iv_size); + } + + return $this->{'_'.$this->_driver.'_decrypt'}($data, $params); + } + + // -------------------------------------------------------------------- + + /** + * Decrypt via MCrypt + * + * @param string $data Encrypted data + * @param array $params Input parameters + * @return string + */ + protected function _mcrypt_decrypt($data, $params) + { + if ( ! is_resource($params['handle'])) + { + return FALSE; + } + elseif (mcrypt_generic_init($params['handle'], $params['key'], $params['iv']) < 0) + { + if ($params['handle'] !== $this->_handle) + { + mcrypt_module_close($params['handle']); + } + + return FALSE; + } + + $data = mdecrypt_generic($params['handle'], $data); + + mcrypt_generic_deinit($params['handle']); + if ($params['handle'] !== $this->_handle) + { + mcrypt_module_close($params['handle']); + } + + // Remove PKCS#7 padding + return substr($data, 0, -ord($data[strlen($data)-1])); + } + + // -------------------------------------------------------------------- + + /** + * Decrypt via OpenSSL + * + * @param string $data Encrypted data + * @param array $params Input parameters + * @return string + */ + protected function _openssl_decrypt($data, $params) + { + return empty($params['handle']) + ? FALSE + : openssl_decrypt( + $data, + $params['handle'], + $params['key'], + 1, // DO NOT TOUCH! + $params['iv'] + ); + } + + // -------------------------------------------------------------------- + + /** + * Get IV size via MCrypt + * + * @param resource $handle MCrypt module resource + * @return int + */ + protected function _mcrypt_get_iv_size($handle) + { + return mcrypt_enc_get_iv_size($handle); + } + + // -------------------------------------------------------------------- + + /** + * Get IV size via OpenSSL + * + * @param string $handle OpenSSL cipher method + * @return int + */ + protected function _openssl_get_iv_size($handle) + { + return openssl_cipher_iv_length($handle); + } + + // -------------------------------------------------------------------- + + /** + * Get IV via MCrypt + * + * @param int $size + * @return int + */ + protected function _mcrypt_get_iv($size) + { + // If /dev/urandom is available - use it, otherwise there's + // also /dev/random, but it is highly unlikely that it would + // be available while /dev/urandom is not and it is known to be + // blocking anyway. + if (defined(MCRYPT_DEV_URANDOM)) + { + $source = MCRYPT_DEV_URANDOM; + } + else + { + $source = MCRYPT_RAND; + is_php('5.3') OR srand(microtime(TRUE)); + } + + return mcrypt_create_iv($size, $source); + } + + // -------------------------------------------------------------------- + + /** + * Get IV via OpenSSL + * + * @param int $size IV size + * @return int + */ + protected function _openssl_get_iv($size) + { + return openssl_random_pseudo_bytes($size); + } + + // -------------------------------------------------------------------- + + /** + * Get params + * + * @param array $params Input parameters + * @return array + */ + protected function _get_params($params) + { + if (empty($params)) + { + return isset($this->_cipher, $this->_mode, $this->_key, $this->_handle) + ? array( + 'handle' => $this->_handle, + 'cipher' => $this->_cipher, + 'mode' => $this->_mode, + 'key' => $this->_key, + 'base64' => TRUE, + 'hmac' => $this->_mode === 'gcm' ? FALSE : array('digest' => 'sha512', 'key' => NULL) + ) + : FALSE; + } + elseif ( ! isset($params['cipher'], $params['mode'], $params['key'])) + { + return FALSE; + } + + if ($params['mode'] === 'gcm') + { + $params['hmac'] = FALSE; + } + elseif ( ! isset($params['hmac']) OR ( ! is_array($params['hmac']) && $params['hmac'] !== FALSE)) + { + $params['hmac'] = array( + 'digest' => 'sha512', + 'key' => NULL + ); + } + elseif (is_array($params['hmac'])) + { + if (isset($params['hmac']['digest']) && ! isset($this->_digests[$params['hmac']['digest']])) + { + return FALSE; + } + + $params['hmac'] = array( + 'digest' => isset($params['hmac']['digest']) ? $params['hmac']['digest'] : 'sha512', + 'key' => isset($params['hmac']['key']) ? $params['hmac']['key'] : NULL + ); + } + + $params = array( + 'handle' => NULL, + 'cipher' => isset($params['cipher']) ? $params['cipher'] : $this->_cipher, + 'mode' => isset($params['mode']) ? $params['mode'] : $this->_mode, + 'key' => isset($params['key']) ? $params['key'] : $this->_key, + 'iv' => isset($params['iv']) ? $params['iv'] : NULL, + 'base64' => isset($params['base64']) ? $params['base64'] : TRUE, + 'hmac' => $params['hmac'] + ); + + $this->_cipher_alias($params['cipher']); + $params['handle'] = ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode) + ? $this->{'_'.$this->_driver.'_get_handle'}($params['cipher'], $params['mode']) + : $this->_handle; + + return $params; + } + + // -------------------------------------------------------------------- + + /** + * Get MCrypt handle + * + * @param string $cipher Cipher name + * @param string $mode Encryption mode + * @return resource + */ + protected function _mcrypt_get_handle($cipher, $mode) + { + return mcrypt_module_open($cipher, '', $mode, ''); + } + + // -------------------------------------------------------------------- + + /** + * Get OpenSSL handle + * + * @param string $cipher Cipher name + * @param string $mode Encryption mode + * @return string + */ + protected function _openssl_get_handle($cipher, $mode) + { + // OpenSSL methods aren't suffixed with '-stream' for this mode + return ($mode === 'stream') + ? $cipher + : $cipher.'-'.$mode; + } + + // -------------------------------------------------------------------- + + /** + * Cipher alias + * + * Tries to translate cipher names between MCrypt and OpenSSL's "dialects". + * + * @param string $cipher Cipher name + * @return void + */ + protected function _cipher_alias(&$cipher) + { + static $dictionary; + + if (empty($dictionary)) + { + $dictionary = array( + 'mcrypt' => array( + 'aes-128' => 'rijndael-128', + 'aes-192' => 'rijndael-128', + 'aes-256' => 'rijndael-128', + 'des3-ede3' => 'tripledes' + ), + 'openssl' => array( + 'rijndael-128' => 'aes-128', + 'tripledes' => 'des-ede3' + ) + ); + + // Notes regarding other seemingly matching ciphers between + // MCrypt and OpenSSL: + // + // - DES is compatible, but doesn't need an alias + // - Blowfish is NOT compatible + // mcrypt: 'blowfish', 'blowfish-compat' + // openssl: 'bf' + // - CAST-128/CAST5 is NOT compatible + // mcrypt: 'cast-128' + // openssl: 'cast5' + // - RC2 is NOT compatible + // mcrypt: 'rc2' + // openssl: 'rc2', 'rc2-40', 'rc2-64' + // + // To avoid any other confusion due to a popular (but incorrect) + // belief, it should also be noted that Rijndael-192/256 are NOT + // the same ciphers as AES-192/256 like Rijndael-128 and AES-256 is. + // + // All compatibility tests were done in CBC mode. + } + + if (isset($dictionary[$this->_driver][$cipher])) + { + $cipher = $dictionary[$this->_driver][$cipher]; + } + } + + // -------------------------------------------------------------------- + + /** + * HKDF + * + * @link https://tools.ietf.org/rfc/rfc5869.txt + * @param $key Input key + * @param $digest A SHA-2 hashing algorithm + * @param $salt Optional salt + * @param $info Optional context/application-specific info + * @param $length Output length (defaults to the selected digest size) + * @return string A pseudo-random key + */ + public function hkdf($key, $digest = 'sha512', $salt = NULL, $length = NULL, $info = '') + { + if ( ! isset($this->_digests[$digest])) + { + return FALSE; + } + + if (empty($length) OR ! is_int($length)) + { + $length = $this->_digests[$digest]; + } + elseif ($length > (255 * $this->_digests[$digest])) + { + return FALSE; + } + + isset($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]); + + $prk = hash_hmac($digest, $key, $salt, TRUE); + $key = ''; + for ($key_block = '', $block_index = 1; strlen($key) < $length; $block_index++) + { + $key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE); + $key .= $key_block; + } + + return substr($key, 0, $length); + } + + // -------------------------------------------------------------------- + + /** + * __get() magic + * + * @param string $key Property name + * @return mixed + */ + public function __get($key) + { + return in_array($key, array('cipher', 'mode', 'driver', 'drivers', 'digests'), TRUE) + ? $this->{'_'.$key} + : NULL; + } + +} + +/* End of file Encryption.php */ +/* Location: ./system/libraries/Encryption.php */
\ No newline at end of file diff --git a/system/libraries/Session/drivers/Session_cookie.php b/system/libraries/Session/drivers/Session_cookie.php index 971dfeabe..5d338fc04 100644 --- a/system/libraries/Session/drivers/Session_cookie.php +++ b/system/libraries/Session/drivers/Session_cookie.php @@ -240,7 +240,7 @@ class CI_Session_cookie extends CI_Session_driver { // Do we need encryption? If so, load the encryption class if ($this->sess_encrypt_cookie === TRUE) { - $this->CI->load->library('encrypt'); + $this->CI->load->library('encryption'); } // Check for database @@ -383,30 +383,33 @@ class CI_Session_cookie extends CI_Session_driver { return FALSE; } - $len = strlen($session) - 40; - - if ($len < 0) + if ($this->sess_encrypt_cookie === TRUE) { - log_message('debug', 'The session cookie was not signed.'); - return FALSE; + $session = $this->CI->encryption->decrypt($session); + if ($session === FALSE) + { + log_message('error', 'Session: Unable to decrypt the session cookie, possibly due to a HMAC mismatch.'); + return FALSE; + } } - - // Check cookie authentication - $hmac = substr($session, $len); - $session = substr($session, 0, $len); - - if ($hmac !== hash_hmac('sha1', $session, $this->encryption_key)) + else { - log_message('error', 'The session cookie data did not match what was expected.'); - $this->sess_destroy(); - return FALSE; - } + if (($len = strlen($session) - 40) <= 0) + { + log_message('error', 'Session: The session cookie was not signed.'); + return FALSE; + } - // Check for encryption - if ($this->sess_encrypt_cookie === TRUE) - { - // Decrypt the cookie data - $session = $this->CI->encrypt->decode($session); + // Check cookie authentication + $hmac = substr($session, $len); + $session = substr($session, 0, $len); + + if ($hmac !== hash_hmac('sha1', $session, $this->encryption_key)) + { + log_message('error', 'Session: HMAC mismatch. The session cookie data did not match what was expected.'); + $this->sess_destroy(); + return FALSE; + } } // Unserialize the session array @@ -723,11 +726,13 @@ class CI_Session_cookie extends CI_Session_driver { if ($this->sess_encrypt_cookie === TRUE) { - $cookie_data = $this->CI->encrypt->encode($cookie_data); + $cookie_data = $this->CI->encryption->encrypt($cookie_data); + } + else + { + // Require message authentication + $cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key); } - - // Require message authentication - $cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key); $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time(); |