diff options
author | Steve Clay <sclay@ufl.edu> | 2013-12-01 20:41:23 -0500 |
---|---|---|
committer | Steve Clay <sclay@ufl.edu> | 2013-12-01 20:41:23 -0500 |
commit | 4ffb40d6b5b9219c2b6964592f58183da6f78962 (patch) | |
tree | d1e67236121f1a27637ed64d3774c0060b82f29b | |
parent | ad8aff634c5c391042484aa9c8129e59faeac093 (diff) | |
parent | 003946eff06fcafe60db5894e1ade0abee7314b4 (diff) | |
download | elgg-4ffb40d6b5b9219c2b6964592f58183da6f78962.tar.gz elgg-4ffb40d6b5b9219c2b6964592f58183da6f78962.tar.bz2 |
Allow regenerating site secret
-rw-r--r-- | actions/admin/site/regenerate_secret.php | 11 | ||||
-rw-r--r-- | engine/classes/ElggCrypto.php | 208 | ||||
-rw-r--r-- | engine/lib/actions.php | 27 | ||||
-rw-r--r-- | engine/lib/admin.php | 2 | ||||
-rw-r--r-- | engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php | 13 | ||||
-rw-r--r-- | languages/en.php | 18 | ||||
-rw-r--r-- | views/default/admin/settings/advanced/site_secret.php | 11 | ||||
-rw-r--r-- | views/default/css/admin.php | 20 | ||||
-rw-r--r-- | views/default/forms/admin/site/regenerate_secret.php | 24 |
9 files changed, 331 insertions, 3 deletions
diff --git a/actions/admin/site/regenerate_secret.php b/actions/admin/site/regenerate_secret.php new file mode 100644 index 000000000..3112fb5f3 --- /dev/null +++ b/actions/admin/site/regenerate_secret.php @@ -0,0 +1,11 @@ +<?php +/** + * Generate a new site secret + */ + +init_site_secret(); +elgg_reset_system_cache(); + +system_message(elgg_echo('admin:site:secret_regenerated')); + +forward(REFERER); diff --git a/engine/classes/ElggCrypto.php b/engine/classes/ElggCrypto.php new file mode 100644 index 000000000..b6a8b2024 --- /dev/null +++ b/engine/classes/ElggCrypto.php @@ -0,0 +1,208 @@ +<?php +/** + * ElggCrypto + * + * @package Elgg.Core + * @subpackage Crypto + * + * @access private + */ +class ElggCrypto { + + /** + * Character set for temp passwords (no risk of embedded profanity/glyphs that look similar) + */ + const CHARS_PASSWORD = 'bcdfghjklmnpqrstvwxyz2346789'; + + /** + * Generate a string of highly randomized bytes (over the full 8-bit range). + * + * @param int $length Number of bytes needed + * @return string Random bytes + * + * @author George Argyros <argyros.george@gmail.com> + * @copyright 2012, George Argyros. All rights reserved. + * @license Modified BSD + * @link https://github.com/GeorgeArgyros/Secure-random-bytes-in-PHP/blob/master/srand.php Original + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the <organization> nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GEORGE ARGYROS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + public function getRandomBytes($length) { + /** + * Our primary choice for a cryptographic strong randomness function is + * openssl_random_pseudo_bytes. + */ + $SSLstr = '4'; // http://xkcd.com/221/ + if (function_exists('openssl_random_pseudo_bytes') + && (version_compare(PHP_VERSION, '5.3.4') >= 0 || substr(PHP_OS, 0, 3) !== 'WIN')) { + $SSLstr = openssl_random_pseudo_bytes($length, $strong); + if ($strong) { + return $SSLstr; + } + } + + /** + * If mcrypt extension is available then we use it to gather entropy from + * the operating system's PRNG. This is better than reading /dev/urandom + * directly since it avoids reading larger blocks of data than needed. + * Older versions of mcrypt_create_iv may be broken or take too much time + * to finish so we only use this function with PHP 5.3.7 and above. + * @see https://bugs.php.net/bug.php?id=55169 + */ + if (function_exists('mcrypt_create_iv') + && (version_compare(PHP_VERSION, '5.3.7') >= 0 || substr(PHP_OS, 0, 3) !== 'WIN')) { + $str = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + if ($str !== false) { + return $str; + } + } + + /** + * No build-in crypto randomness function found. We collect any entropy + * available in the PHP core PRNGs along with some filesystem info and memory + * stats. To make this data cryptographically strong we add data either from + * /dev/urandom or if its unavailable, we gather entropy by measuring the + * time needed to compute a number of SHA-1 hashes. + */ + $str = ''; + $bits_per_round = 2; // bits of entropy collected in each clock drift round + $msec_per_round = 400; // expected running time of each round in microseconds + $hash_len = 20; // SHA-1 Hash length + $total = $length; // total bytes of entropy to collect + + $handle = @fopen('/dev/urandom', 'rb'); + if ($handle && function_exists('stream_set_read_buffer')) { + @stream_set_read_buffer($handle, 0); + } + + do { + $bytes = ($total > $hash_len)? $hash_len : $total; + $total -= $bytes; + + //collect any entropy available from the PHP system and filesystem + $entropy = rand() . uniqid(mt_rand(), true) . $SSLstr; + $entropy .= implode('', @fstat(@fopen( __FILE__, 'r'))); + $entropy .= memory_get_usage() . getmypid(); + $entropy .= serialize($_ENV) . serialize($_SERVER); + if (function_exists('posix_times')) { + $entropy .= serialize(posix_times()); + } + if (function_exists('zend_thread_id')) { + $entropy .= zend_thread_id(); + } + + if ($handle) { + $entropy .= @fread($handle, $bytes); + } else { + // Measure the time that the operations will take on average + for ($i = 0; $i < 3; $i++) { + $c1 = microtime(true); + $var = sha1(mt_rand()); + for ($j = 0; $j < 50; $j++) { + $var = sha1($var); + } + $c2 = microtime(true); + $entropy .= $c1 . $c2; + } + + // Based on the above measurement determine the total rounds + // in order to bound the total running time. + $rounds = (int) ($msec_per_round * 50 / (int) (($c2 - $c1) * 1000000)); + + // Take the additional measurements. On average we can expect + // at least $bits_per_round bits of entropy from each measurement. + $iter = $bytes * (int) (ceil(8 / $bits_per_round)); + + for ($i = 0; $i < $iter; $i++) { + $c1 = microtime(); + $var = sha1(mt_rand()); + for ($j = 0; $j < $rounds; $j++) { + $var = sha1($var); + } + $c2 = microtime(); + $entropy .= $c1 . $c2; + } + } + + // We assume sha1 is a deterministic extractor for the $entropy variable. + $str .= sha1($entropy, true); + + } while ($length > strlen($str)); + + if ($handle) { + @fclose($handle); + } + + return substr($str, 0, $length); + } + + /** + * Generate a random string of specified length. + * + * Uses supplied character list for generating the new string. + * If no character list provided - uses Base64 URL character set. + * + * @param int $length Desired length of the string + * @param string|null $chars Characters to be chosen from randomly. If not given, the Base64 URL + * charset will be used. + * + * @return string The random string + * + * @throws InvalidArgumentException + * + * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * + * @see https://github.com/zendframework/zf2/blob/master/library/Zend/Math/Rand.php#L179 + */ + public static function getRandomString($length, $chars = null) { + if ($length < 1) { + throw new InvalidArgumentException('Length should be >= 1'); + } + + if (empty($chars)) { + $numBytes = ceil($length * 0.75); + $bytes = self::getRandomBytes($numBytes); + $string = substr(rtrim(base64_encode($bytes), '='), 0, $length); + + // Base64 URL + return strtr($string, '+/', '-_'); + } + + $listLen = strlen($chars); + + if ($listLen == 1) { + return str_repeat($chars, $length); + } + + $bytes = self::getRandomBytes($length); + $pos = 0; + $result = ''; + for ($i = 0; $i < $length; $i++) { + $pos = ($pos + ord($bytes[$i])) % $listLen; + $result .= $chars[$pos]; + } + + return $result; + } +} diff --git a/engine/lib/actions.php b/engine/lib/actions.php index 56936f582..8047914ac 100644 --- a/engine/lib/actions.php +++ b/engine/lib/actions.php @@ -364,16 +364,19 @@ function generate_action_token($timestamp) { } /** - * Initialise the site secret hash. + * Initialise the site secret (32 bytes: "z" to indicate format + 186-bit key in Base64 URL). * * Used during installation and saves as a datalist. * + * Note: Old secrets were hex encoded. + * * @return mixed The site secret hash or false * @access private * @todo Move to better file. */ function init_site_secret() { - $secret = md5(rand() . microtime()); + $secret = 'z' . ElggCrypto::getRandomString(31); + if (datalist_set('__site_secret__', $secret)) { return $secret; } @@ -400,6 +403,26 @@ function get_site_secret() { } /** + * Get the strength of the site secret + * + * @return string "strong", "moderate", or "weak" + * @access private + */ +function _elgg_get_site_secret_strength() { + $secret = get_site_secret(); + if ($secret[0] !== 'z') { + $rand_max = getrandmax(); + if ($rand_max < pow(2, 16)) { + return 'weak'; + } + if ($rand_max < pow(2, 32)) { + return 'moderate'; + } + } + return 'strong'; +} + +/** * Check if an action is registered and its script exists. * * @param string $action Action name diff --git a/engine/lib/admin.php b/engine/lib/admin.php index 7f82108c0..f36f29668 100644 --- a/engine/lib/admin.php +++ b/engine/lib/admin.php @@ -236,6 +236,7 @@ function admin_init() { elgg_register_action('admin/site/update_advanced', '', 'admin'); elgg_register_action('admin/site/flush_cache', '', 'admin'); elgg_register_action('admin/site/unlock_upgrade', '', 'admin'); + elgg_register_action('admin/site/regenerate_secret', '', 'admin'); elgg_register_action('admin/menu/save', '', 'admin'); @@ -291,6 +292,7 @@ function admin_init() { elgg_register_admin_menu_item('configure', 'settings', null, 100); elgg_register_admin_menu_item('configure', 'basic', 'settings', 10); elgg_register_admin_menu_item('configure', 'advanced', 'settings', 20); + elgg_register_admin_menu_item('configure', 'advanced/site_secret', 'settings', 25); elgg_register_admin_menu_item('configure', 'menu_items', 'appearance', 30); elgg_register_admin_menu_item('configure', 'profile_fields', 'appearance', 40); // default widgets is added via an event handler elgg_default_widgets_init() in widgets.php diff --git a/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php b/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php new file mode 100644 index 000000000..b5b614762 --- /dev/null +++ b/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php @@ -0,0 +1,13 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013060900 + * site_secret + * + * Description + */ + +$strength = _elgg_get_site_secret_strength(); + +if ($strength !== 'strong') { + elgg_add_admin_notice('weak_site_key', elgg_echo("upgrade:site_secret_warning:$strength")); +} diff --git a/languages/en.php b/languages/en.php index ad4831db7..4eed63d4c 100644 --- a/languages/en.php +++ b/languages/en.php @@ -597,10 +597,24 @@ $english = array( 'admin:settings' => 'Settings', 'admin:settings:basic' => 'Basic Settings', 'admin:settings:advanced' => 'Advanced Settings', + 'admin:settings:advanced/site_secret' => 'Site Secret', 'admin:site:description' => "This admin panel allows you to control global settings for your site. Choose an option below to get started.", + 'admin:settings:advanced:site_secret' => 'Site Secret', 'admin:site:opt:linktext' => "Configure site...", 'admin:site:access:warning' => "Changing the access setting only affects the permissions on content created in the future.", + 'admin:site:secret:intro' => 'Elgg uses a key to create security tokens for various purposes.', + 'admin:site:secret_regenerated' => "Your site secret has been regenerated.", + 'admin:site:secret:regenerate' => "Regenerate site secret", + 'admin:site:secret:regenerate:help' => "Note: This may inconvenience some users by invalidating tokens used in \"remember me\" cookies, e-mail validation requests, invitation codes, etc.", + 'site_secret:current_strength' => 'Key Strength', + 'site_secret:strength:weak' => "Weak", + 'site_secret:strength_msg:weak' => "We strongly recommend that you regenerate your site secret.", + 'site_secret:strength:moderate' => "Moderate", + 'site_secret:strength_msg:moderate' => "We recommend you regenerate your site secret for the best site security.", + 'site_secret:strength:strong' => "Strong", + 'site_secret:strength_msg:strong' => "✓ Your site secret is sufficiently strong.", + 'admin:dashboard' => 'Dashboard', 'admin:widget:online_users' => 'Online users', 'admin:widget:online_users:help' => 'Lists the users currently on the site', @@ -1064,7 +1078,7 @@ Once you have logged in, we highly recommend that you change your password. 'upgrade:unlock' => 'Unlock upgrade', 'upgrade:unlock:confirm' => "The database is locked for another upgrade. Running concurrent upgrades is dangerous. You should only continue if you know there is not another upgrade running. Unlock?", 'upgrade:locked' => "Cannot upgrade. Another upgrade is running. To clear the upgrade lock, visit the Admin section.", - 'upgrade:unlock:success' => "Upgrade unlocked suscessfully.", + 'upgrade:unlock:success' => "Upgrade unlocked successfully.", 'upgrade:unable_to_upgrade' => 'Unable to upgrade.', 'upgrade:unable_to_upgrade_info' => 'This installation cannot be upgraded because legacy views @@ -1079,6 +1093,8 @@ Once you have logged in, we highly recommend that you change your password. 'update:twitter_api:deactivated' => 'Twitter API (previously Twitter Service) was deactivated during the upgrade. Please activate it manually if required.', 'update:oauth_api:deactivated' => 'OAuth API (previously OAuth Lib) was deactivated during the upgrade. Please activate it manually if required.', + 'upgrade:site_secret_warning:moderate' => "You are encouraged to regenerate your site key to improve system security. See Configure > Site Secret", + 'upgrade:site_secret_warning:weak' => "You are strongly encouraged to regenerate your site key to improve system security. See Configure > Site Secret", 'deprecated:function' => '%s() was deprecated by %s()', diff --git a/views/default/admin/settings/advanced/site_secret.php b/views/default/admin/settings/advanced/site_secret.php new file mode 100644 index 000000000..e70ac7ab6 --- /dev/null +++ b/views/default/admin/settings/advanced/site_secret.php @@ -0,0 +1,11 @@ +<?php +/** + * Elgg administration site secret settings + * + * @package Elgg + * @subpackage Core + */ + +echo elgg_view_form('admin/site/regenerate_secret', array(), array( + 'strength' => _elgg_get_site_secret_strength(), +)); diff --git a/views/default/css/admin.php b/views/default/css/admin.php index 3896ded5d..c435621b2 100644 --- a/views/default/css/admin.php +++ b/views/default/css/admin.php @@ -1544,6 +1544,26 @@ table.mceLayout { } /* *************************************** + SITE SECRET +*************************************** */ +.elgg-form-admin-site-regenerate-secret table { + width: 60%; + margin: 1em auto; +} +td.elgg-strength-strong, +td.elgg-strength-strong h4 { + background: #DFF0D8; color: #468847; +} +td.elgg-strength-moderate, +td.elgg-strength-moderate h4 { + background: #FCF8E3; color: #C09853; +} +td.elgg-strength-weak, +td.elgg-strength-weak h4 { + background: #F2DEDE; color: #B94A48; +} + +/* *************************************** HELPERS *************************************** */ .hidden { diff --git a/views/default/forms/admin/site/regenerate_secret.php b/views/default/forms/admin/site/regenerate_secret.php new file mode 100644 index 000000000..af269b801 --- /dev/null +++ b/views/default/forms/admin/site/regenerate_secret.php @@ -0,0 +1,24 @@ +<?php + +$strength = $vars['strength']; + +?> +<p><?php echo elgg_echo('admin:site:secret:intro'); ?></p> + +<table class="elgg-table"> + <tr> + <th><?php echo elgg_echo('site_secret:current_strength'); ?></th> + <td class="elgg-strength-<?php echo $strength; ?>"> + <h4><?php echo elgg_echo("site_secret:strength:$strength"); ?></h4> + <div><?php echo elgg_echo("site_secret:strength_msg:$strength"); ?></div> + </td> + </tr> +</table> + +<div class="elgg-foot"> + <?php echo elgg_view('input/submit', array( + 'value' => elgg_echo('admin:site:secret:regenerate'), + 'class' => 'elgg-requires-confirmation elgg-button elgg-button-submit', + )); ?> + <p class="elgg-text-help mts"><?php echo elgg_echo('admin:site:secret:regenerate:help'); ?></p> +</div> |