diff --git a/tfa.admin.inc b/tfa.admin.inc index 28319af..c063e08 100644 --- a/tfa.admin.inc +++ b/tfa.admin.inc @@ -25,10 +25,10 @@ function tfa_admin_settings($form, $form_state) { } } - // Check if mcrypt plugin is available. - if (!extension_loaded('mcrypt')) { + // Check if openssl or mcrypt extensions are available. + if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) { // @todo allow alter in case of other encryption libs. - drupal_set_message(t('The TFA module requires the PHP Mcrypt extension be installed on the web server. See the TFA help documentation for setup.', array('!link' => url('admin/help/tfa'))), 'error'); + drupal_set_message(t('The TFA module requires one of the PHP OpenSSL or MCrypt extensions to be installed on the web server. See the TFA help documentation for setup.', array('!link' => url('admin/help/tfa'))), 'error'); return array(); } diff --git a/tfa.inc b/tfa.inc index bb9ab82..9571fe5 100644 --- a/tfa.inc +++ b/tfa.inc @@ -376,6 +376,8 @@ public function getContext() { */ abstract class TfaBasePlugin { + const CRYPT_VERSION = '1'; + /** * @var string */ @@ -502,10 +504,39 @@ protected function generate() { * * Should be used when writing codes to storage. * - * @param string. + * @param string $text + * The plaintext to be encrypted. + * * @return string + * The encrypted text. */ protected function encrypt($text) { + // Backwards compatibility with MCrypt. + if (!extension_loaded('openssl') && extension_loaded('mcrypt')) { + return $this->encryptWithMCrypt($text); + } + $iv = drupal_random_bytes(16); + // Using 1 instead of the constant OPENSSL_RAW_DATA, for PHP 5.3. + $ciphertext = openssl_encrypt($text, 'AES-256-CBC', $this->encryptionKey, 1, $iv); + $crypto_data = array( + 'version' => self::CRYPT_VERSION, + 'iv_base64' => base64_encode($iv), + 'ciphertext_base64' => base64_encode($ciphertext), + ); + $json_encoded_crypto_data = drupal_json_encode($crypto_data); + return $json_encoded_crypto_data; + } + + /** + * Encrypt using the deprecated MCrypt extension. + * + * @param string $text + * The text to encrypt. + * + * @return string + * The text encrypted using MCrypt. + */ + protected function encryptWithMCrypt($text) { $td = mcrypt_module_open('rijndael-128', '', 'cbc', ''); $iv = drupal_random_bytes(mcrypt_enc_get_iv_size($td)); @@ -528,10 +559,42 @@ protected function encrypt($text) { * * Should be used when reading codes from storage. * - * @param string - * @return string + * @param string $data + * The encrypted text. + * + * @return string|boolean + * The plaintext, or FALSE on failure. */ protected function decrypt($data) { + $is_legacy = TRUE; + $crypto_data = drupal_json_decode($data); + if (!empty($crypto_data['version']) && !empty($crypto_data['iv_base64']) && !empty($crypto_data['ciphertext_base64'])) { + $iv = base64_decode($crypto_data['iv_base64']); + $ciphertext = base64_decode($crypto_data['ciphertext_base64']); + return openssl_decrypt($ciphertext, 'AES-256-CBC', $this->encryptionKey, TRUE, $iv); + } + + // Backwards compatibility with the old MCrypt scheme. + if (extension_loaded('openssl')) { + return $this->decryptLegacyDataWithOpenSSL($data); + } + if (extension_loaded('mcrypt')) { + return $this->decryptLegacyDataWithMCrypt($data); + } + + return FALSE; + } + + /** + * Decrypt using the deprecated MCrypt extension. + * + * @param string $data + * The data to be decrypted. + * + * @return string|boolean + * The plaintext, or FALSE on failure. + */ + protected function decryptLegacyDataWithMCrypt($data) { $td = mcrypt_module_open('rijndael-128', '', 'cbc', ''); $iv = substr($data, 0, mcrypt_enc_get_iv_size($td)); @@ -551,6 +614,37 @@ protected function decrypt($data) { return $text; } + /** + * Use OpenSSL to decrypt data that was originally encrypted with MCrypt. + * + * As used by an earlier version of this module. + * + * @param string $data + * The data to be decrypted. + * + * @return string|boolean + * The plaintext, or FALSE on failure. + */ + protected function decryptLegacyDataWithOpenSSL($data) { + $key_size = 32; // Based on return value of mcrypt_enc_get_key_size($td). + $iv_size = 16; // Based on return value of mcrypt_enc_get_iv_size($td). + $key = substr($this->encryptionKey, 0, $key_size); + $iv = substr($data, 0, $iv_size); + $data = substr($data, $iv_size); + // Using 3 instead of the constant OPENSSL_NO_PADDING, for PHP 5.3. + $decrypted_text = openssl_decrypt($data, 'AES-256-CBC', $key, 3, $iv); + if ($decrypted_text === FALSE) { + return FALSE; + } + + // Return only the message and none of its padding. + if (strpos($decrypted_text, '|') !== FALSE) { + list($length, $padded_data) = explode('|', $decrypted_text, 2); + $decrypted_text = substr($padded_data, 0, $length); + } + + return $decrypted_text; + } } /** @@ -565,8 +659,12 @@ interface TfaValidationPluginInterface { * Get TFA process form from plugin. * * @param array $form + * The form array structure. * @param array $form_state - * @return array Form API array. + * The current form state array. + * + * @return array + * Form API array. */ public function getForm(array $form, array &$form_state); @@ -574,8 +672,12 @@ public function getForm(array $form, array &$form_state); * Validate form. * * @param array $form + * The form array structure. * @param array $form_state - * @return bool Whether form passes validation or not + * The current form state array. + * + * @return bool + * Whether form passes validation or not. */ public function validateForm(array $form, array &$form_state); } @@ -592,6 +694,7 @@ interface TfaLoginPluginInterface { * Whether authentication should be interrupted. * * @return bool + * Indicates whether authentication should be interrupted. */ public function loginAllowed(); } @@ -623,20 +726,28 @@ interface TfaSetupPluginInterface { /** * @param array $form + * The form array structure. * @param array $form_state + * The current form state array. */ public function getSetupForm(array $form, array &$form_state); /** * @param array $form + * The form array structure. * @param array $form_state + * The current form state array. */ public function validateSetupForm(array $form, array &$form_state); /** * @param array $form + * The form array structure. * @param array $form_state + * The current form state array. + * * @return bool + * Indicates whether the form submission succeeded. */ public function submitSetupForm(array $form, array &$form_state); diff --git a/tfa.install b/tfa.install index 9d8adf3..8471e27 100644 --- a/tfa.install +++ b/tfa.install @@ -27,13 +27,19 @@ function tfa_uninstall() { function tfa_requirements($phase) { $t = get_t(); if ($phase == 'runtime') { - if (!extension_loaded('mcrypt')) { - $requirement_severity = REQUIREMENT_ERROR; - $description = $t('The TFA module requires the PHP Mcrypt extension be installed on the web server.'); + if (!extension_loaded('openssl')) { + if (extension_loaded('mcrypt')) { + $requirement_severity = REQUIREMENT_WARNING; + $description = t('The TFA module recommends the PHP OpenSSL extension to be installed on the web server.'); + } + else { + $requirement_severity = REQUIREMENT_ERROR; + $description = t('The TFA module requires either the PHP OpenSSL or MCrypt extensions to be installed on the web server.'); + } } else { $requirement_severity = REQUIREMENT_OK; - $description= ''; + $description = ''; } $enabled = variable_get('tfa_enabled', 0);