diff --git a/app/Config/Encryption.php b/app/Config/Encryption.php index 640bd5860..cf7da5878 100644 --- a/app/Config/Encryption.php +++ b/app/Config/Encryption.php @@ -57,7 +57,15 @@ class Encryption extends BaseConfig */ public string $digest = 'SHA512'; - /** + /** + * The cipher to use. + * This setting is only used by OpenSSLHandler. + * + * Set to aes-128-cbc for CI3 Encryption compatibility. + */ + public string $cipher = 'aes-256-ctr'; + + /** * Whether the cipher-text should be raw. If set to false, then it will be base64 encoded. * This setting is only used by OpenSSLHandler. * diff --git a/app/Controllers/Config.php b/app/Controllers/Config.php index a283ccfc7..24b150ba1 100644 --- a/app/Controllers/Config.php +++ b/app/Controllers/Config.php @@ -7,6 +7,7 @@ use App\Libraries\Mailchimp_lib; use App\Libraries\Receiving_lib; use App\Libraries\Sale_lib; use App\Libraries\Tax_lib; +use App\Libraries\Ci3encrypt; use App\Models\Appconfig; use App\Models\Attribute; @@ -17,9 +18,9 @@ use App\Models\Enums\Rounding_mode; use App\Models\Stock_location; use App\Models\Tax; -use CodeIgniter\Encryption\Encryption; use CodeIgniter\Encryption\EncrypterInterface; use CodeIgniter\Files\File; +use Config\Encryption; use Config\Services; use DirectoryIterator; use NumberFormatter; @@ -278,10 +279,18 @@ class Config extends Secure_Controller if($this->_check_encryption()) //TODO: Hungarian notation { - $encrypter = Services::encrypter(); + $config = new Encryption(); + $config->driver = 'OpenSSL'; + $config->key = config('Encryption')->key; + $config->cipher = 'AES-128-CBC'; + $config->rawData = false; + $config->encryptKeyInfo = 'encryption'; + $config->authKeyInfo = 'authentication'; - $data['mailchimp']['api_key'] = $encrypter->decrypt($this->config['mailchimp_api_key']); //TODO: When these keys don't exist decrypt throws an exception. We need to rework this so that if the array elements are null that it doesn't try to call decrypt. Here and in other places. - $data['mailchimp']['list_id'] = $encrypter->decrypt($this->config['mailchimp_list_id']); + $encrypter = Services::encrypter($config, false); + + $data['mailchimp']['api_key'] = $encrypter->decrypt($this->config['mailchimp_api_key'] ?? ''); + $data['mailchimp']['list_id'] = $encrypter->decrypt($this->config['mailchimp_list_id'] ?? ''); } else { @@ -289,7 +298,6 @@ class Config extends Secure_Controller $data['mailchimp']['list_id'] = ''; } - // load mailchimp lists associated to the given api key, already XSS cleaned in the private function $data['mailchimp']['lists'] = $this->_mailchimp(); echo view('configs/manage', $data); diff --git a/app/Libraries/Ci3encrypt.php b/app/Libraries/Ci3encrypt.php deleted file mode 100644 index a57f59b63..000000000 --- a/app/Libraries/Ci3encrypt.php +++ /dev/null @@ -1,942 +0,0 @@ - array( - 'cbc' => 'cbc', - 'ecb' => 'ecb', - 'ofb' => 'nofb', - 'ofb8' => 'ofb', - 'cfb' => 'ncfb', - 'cfb8' => 'cfb', - 'ctr' => 'ctr', - 'stream' => 'stream' - ), - 'openssl' => array( - 'cbc' => 'cbc', - 'ecb' => 'ecb', - 'ofb' => 'ofb', - 'cfb' => 'cfb', - 'cfb8' => 'cfb8', - 'ctr' => 'ctr', - 'stream' => '', - 'xts' => 'xts' - ) - ); - - /** - * List of supported HMAC algorithms - * - * name => digest size pairs - * - * @var array - */ - protected $_digests = array( - 'sha224' => 28, - 'sha256' => 32, - 'sha384' => 48, - 'sha512' => 64 - ); - - /** - * mbstring.func_overload flag - * - * @var bool - */ - protected static $func_overload; - - // -------------------------------------------------------------------- - - /** - * Class constructor - * - * @param array $params Configuration parameters - * @return void - */ - public function __construct(array $params = array()) - { - $this->_drivers = array( - 'mcrypt' => defined('MCRYPT_DEV_URANDOM'), - 'openssl' => extension_loaded('openssl') - ); - - if ( ! $this->_drivers['mcrypt'] && ! $this->_drivers['openssl']) - { - show_error('Encryption: Unable to find an available encryption driver.'); - } - - isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); - $this->initialize($params); - - if ( ! isset($this->_key) && self::strlen($key = config('Encryption')->key) > 0) - { - $this->_key = $key; - } - - log_message('info', 'Encryption Class Initialized'); - } - - // -------------------------------------------------------------------- - - /** - * Initialize - * - * @param array $params Configuration parameters - * @return Ci3encrypt - */ - 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['openssl'] === TRUE) - ? 'openssl' - : 'mcrypt'; - - log_message('debug', "Encryption: Auto-configured driver '".$this->_driver."'."); - } - - empty($params['cipher']) && $params['cipher'] = $this->_cipher; - 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'])) - { - $params['mode'] = strtolower($params['mode']); - if ( ! isset($this->_modes['mcrypt'][$params['mode']])) - { - log_message('error', 'Encryption: MCrypt mode '.strtoupper($params['mode']).' is not available.'); - } - else - { - $this->_mode = $this->_modes['mcrypt'][$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('info', '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'])) - { - $params['mode'] = strtolower($params['mode']); - if ( ! isset($this->_modes['openssl'][$params['mode']])) - { - log_message('error', 'Encryption: OpenSSL mode '.strtoupper($params['mode']).' is not available.'); - } - else - { - $this->_mode = $this->_modes['openssl'][$params['mode']]; - } - } - - if (isset($this->_cipher, $this->_mode)) - { - // This is mostly for the stream mode, which doesn't get suffixed in OpenSSL - $handle = empty($this->_mode) - ? $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('info', 'Encryption: OpenSSL initialized with method '.strtoupper($handle).'.'); - } - } - } - - // -------------------------------------------------------------------- - - /** - * Create a random key - * - * @param int $length Output length - * @return string - */ - public function create_key($length) - { - if (function_exists('random_bytes')) - { - try - { - return random_bytes((int) $length); - } - catch (Exception $e) - { - log_message('error', $e->getMessage()); - return FALSE; - } - } - elseif (defined('MCRYPT_DEV_URANDOM')) - { - return mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); - } - - $is_secure = NULL; - $key = openssl_random_pseudo_bytes($length, $is_secure); - return ($is_secure === TRUE) - ? $key - : FALSE; - } - - // -------------------------------------------------------------------- - - /** - * 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; - } - - isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption'); - - if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE) - { - return FALSE; - } - - $params['base64'] && $data = base64_encode($data); - - if (isset($params['hmac_digest'])) - { - isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', 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; - } - - // The greater-than-1 comparison is mostly a work-around for a bug, - // where 1 is returned for ARCFour instead of 0. - $iv = (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1) - ? $this->create_key($iv_size) - : NULL; - - if (mcrypt_generic_init($params['handle'], $params['key'], $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. - if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE)) - { - $block_size = mcrypt_enc_get_block_size($params['handle']); - $pad = $block_size - (self::strlen($data) % $block_size); - $data .= str_repeat(chr($pad), $pad); - } - - // Work-around for yet another strange behavior in MCrypt. - // - // When encrypting in ECB mode, the IV is ignored. Yet - // mcrypt_enc_get_iv_size() returns a value larger than 0 - // even if ECB is used AND mcrypt_generic_init() complains - // if you don't pass an IV with length equal to the said - // return value. - // - // This probably would've been fine (even though still wasteful), - // but OpenSSL isn't that dumb and we need to make the process - // portable, so ... - $data = (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB') - ? $iv.mcrypt_generic($params['handle'], $data) - : 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; - } - - $iv = ($iv_size = openssl_cipher_iv_length($params['handle'])) - ? $this->create_key($iv_size) - : NULL; - - $data = openssl_encrypt( - $data, - $params['handle'], - $params['key'], - 1, // DO NOT TOUCH! - $iv - ); - - if ($data === FALSE) - { - return FALSE; - } - - return $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 (isset($params['hmac_digest'])) - { - // 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']]; - - if (self::strlen($data) <= $digest_size) - { - return FALSE; - } - - $hmac_input = self::substr($data, 0, $digest_size); - $data = self::substr($data, $digest_size); - - isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication'); - $hmac_check = hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']); - - // Time-attack-safe comparison - $diff = 0; - for ($i = 0; $i < $digest_size; $i++) - { - $diff |= ord($hmac_input[$i]) ^ ord($hmac_check[$i]); - } - - if ($diff !== 0) - { - return FALSE; - } - } - - if ($params['base64']) - { - $data = base64_decode($data); - } - - isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption'); - - 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; - } - - // The greater-than-1 comparison is mostly a work-around for a bug, - // where 1 is returned for ARCFour instead of 0. - if (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1) - { - if (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB') - { - $iv = self::substr($data, 0, $iv_size); - $data = self::substr($data, $iv_size); - } - else - { - // MCrypt is dumb and this is ignored, only size matters - $iv = str_repeat("\x0", $iv_size); - } - } - else - { - $iv = NULL; - } - - if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0) - { - if ($params['handle'] !== $this->_handle) - { - mcrypt_module_close($params['handle']); - } - - return FALSE; - } - - $data = mdecrypt_generic($params['handle'], $data); - // Remove PKCS#7 padding, if necessary - if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE)) - { - $data = self::substr($data, 0, -ord($data[self::strlen($data)-1])); - } - - mcrypt_generic_deinit($params['handle']); - if ($params['handle'] !== $this->_handle) - { - mcrypt_module_close($params['handle']); - } - - return $data; - } - - // -------------------------------------------------------------------- - - /** - * Decrypt via OpenSSL - * - * @param string $data Encrypted data - * @param array $params Input parameters - * @return string - */ - protected function _openssl_decrypt($data, $params) - { - if ($iv_size = openssl_cipher_iv_length($params['handle'])) - { - $iv = self::substr($data, 0, $iv_size); - $data = self::substr($data, $iv_size); - } - else - { - $iv = NULL; - } - - return empty($params['handle']) - ? FALSE - : openssl_decrypt( - $data, - $params['handle'], - $params['key'], - 1, // DO NOT TOUCH! - $iv - ); - } - - // -------------------------------------------------------------------- - - /** - * 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' => NULL, - 'base64' => TRUE, - 'hmac_digest' => 'sha512', - 'hmac_key' => NULL - ) - : FALSE; - } - elseif ( ! isset($params['cipher'], $params['mode'], $params['key'])) - { - return FALSE; - } - - if (isset($params['mode'])) - { - $params['mode'] = strtolower($params['mode']); - if ( ! isset($this->_modes[$this->_driver][$params['mode']])) - { - return FALSE; - } - - $params['mode'] = $this->_modes[$this->_driver][$params['mode']]; - } - - if (isset($params['hmac']) && $params['hmac'] === FALSE) - { - $params['hmac_digest'] = $params['hmac_key'] = NULL; - } - else - { - if ( ! isset($params['hmac_key'])) - { - return FALSE; - } - elseif (isset($params['hmac_digest'])) - { - $params['hmac_digest'] = strtolower($params['hmac_digest']); - if ( ! isset($this->_digests[$params['hmac_digest']])) - { - return FALSE; - } - } - else - { - $params['hmac_digest'] = 'sha512'; - } - } - - $params = array( - 'handle' => NULL, - 'cipher' => $params['cipher'], - 'mode' => $params['mode'], - 'key' => $params['key'], - 'base64' => isset($params['raw_data']) ? ! $params['raw_data'] : FALSE, - 'hmac_digest' => $params['hmac_digest'], - 'hmac_key' => $params['hmac_key'] - ); - - $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', - 'bf' => 'blowfish', - 'cast5' => 'cast-128', - 'rc4' => 'arcfour', - 'rc4-40' => 'arcfour' - ), - 'openssl' => array( - 'rijndael-128' => 'aes-128', - 'tripledes' => 'des-ede3', - 'blowfish' => 'bf', - 'cast-128' => 'cast5', - 'arcfour' => 'rc4-40', - 'rc4' => 'rc4-40' - ) - ); - - // Notes: - // - // - Rijndael-128 is, at the same time all three of AES-128, - // AES-192 and AES-256. The only difference between them is - // the key size. Rijndael-192, Rijndael-256 on the other hand - // also have different block sizes and are NOT AES-compatible. - // - // - Blowfish is said to be supporting key sizes between - // 4 and 56 bytes, but it appears that between MCrypt and - // OpenSSL, only those of 16 and more bytes are compatible. - // Also, don't know what MCrypt's 'blowfish-compat' is. - // - // - CAST-128/CAST5 produces a longer cipher when encrypted via - // OpenSSL, but (strangely enough) can be decrypted by either - // extension anyway. - // Also, it appears that OpenSSL uses 16 rounds regardless of - // the key size, while RFC2144 says that for key sizes lower - // than 11 bytes, only 12 rounds should be used. This makes - // it portable only with keys of between 11 and 16 bytes. - // - // - RC4 (ARCFour) has a strange implementation under OpenSSL. - // Its 'rc4-40' cipher method seems to work flawlessly, yet - // there's another one, 'rc4' that only works with a 16-byte key. - // - // - DES is compatible, but doesn't need an alias. - // - // Other seemingly matching ciphers between MCrypt, OpenSSL: - // - // - RC2 is NOT compatible and only an obscure forum post - // confirms that it is MCrypt's fault. - } - - 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 $length Output length (defaults to the selected digest size) - * @param $info Optional context/application-specific info - * @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; - } - - self::strlen($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]); - - $prk = hash_hmac($digest, $key, $salt, TRUE); - $key = ''; - for ($key_block = '', $block_index = 1; self::strlen($key) < $length; $block_index++) - { - $key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE); - $key .= $key_block; - } - - return self::substr($key, 0, $length); - } - - // -------------------------------------------------------------------- - - /** - * __get() magic - * - * @param string $key Property name - * @return mixed - */ - public function __get($key) - { - // Because aliases - if ($key === 'mode') - { - return array_search($this->_mode, $this->_modes[$this->_driver], TRUE); - } - elseif (in_array($key, array('cipher', 'driver', 'drivers', 'digests'), TRUE)) - { - return $this->{'_'.$key}; - } - - return NULL; - } - - // -------------------------------------------------------------------- - - /** - * Byte-safe strlen() - * - * @param string $str - * @return int - */ - protected static function strlen($str) - { - return (self::$func_overload) - ? mb_strlen($str, '8bit') - : strlen($str); - } - - // -------------------------------------------------------------------- - - /** - * Byte-safe substr() - * - * @param string $str - * @param int $start - * @param int $length - * @return string - */ - protected static function substr($str, $start, $length = NULL) - { - if (self::$func_overload) - { - // mb_substr($str, $start, null, '8bit') returns an empty - // string on PHP 5.3 - isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start); - return mb_substr($str, $start, $length, '8bit'); - } - - return isset($length) - ? substr($str, $start, $length) - : substr($str, $start); - } -}