]> gitweb.fluxo.info Git - lorea/elgg.git/commitdiff
Allow regenerating site secret
authorSteve Clay <steve@mrclay.org>
Tue, 11 Jun 2013 03:16:45 +0000 (23:16 -0400)
committerPaweł Sroka <srokap@gmail.com>
Mon, 4 Nov 2013 03:24:47 +0000 (04:24 +0100)
actions/admin/site/regenerate_secret.php [new file with mode: 0644]
engine/classes/ElggCrypto.php [new file with mode: 0644]
engine/lib/actions.php
engine/lib/admin.php
engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php [new file with mode: 0644]
languages/en.php
views/default/admin/settings/advanced/site_secret.php [new file with mode: 0644]
views/default/css/admin.php
views/default/forms/admin/site/regenerate_secret.php [new file with mode: 0644]

diff --git a/actions/admin/site/regenerate_secret.php b/actions/admin/site/regenerate_secret.php
new file mode 100644 (file)
index 0000000..3112fb5
--- /dev/null
@@ -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 (file)
index 0000000..364af45
--- /dev/null
@@ -0,0 +1,134 @@
+<?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';
+
+       /**
+        * Returns a string of highly randomized bytes (over the full 8-bit range).
+        *
+        * This function is better than simply calling mt_rand() or any other built-in
+        * PHP function because it can return a long string of bytes (compared to < 4
+        * bytes normally from mt_rand()) and uses the best available pseudo-random
+        * source.
+        *
+        * @param int $count The number of characters (bytes) to return in the string.
+        * @return string
+        *
+        * @copyright Copyright 2001 - 2012 by the original authors
+        *            https://github.com/drupal/drupal/blob/7.x/COPYRIGHT.txt
+        * @license   https://github.com/drupal/drupal/blob/7.x/LICENSE.txt GPL 2
+        *
+        * @see https://github.com/drupal/drupal/blob/7.x/includes/bootstrap.inc#L1942
+        */
+       public static function getRandomBytes($count)  {
+               // $random_state does not use drupal_static as it stores random bytes.
+               static $random_state, $bytes, $php_compatible;
+               // Initialize on the first call. The contents of $_SERVER includes a mix of
+               // user-specific and system information that varies a little with each page.
+               if (!isset($random_state)) {
+                       $random_state = print_r($_SERVER, true);
+                       if (function_exists('getmypid')) {
+                               // Further initialize with the somewhat random PHP process ID.
+                               $random_state .= getmypid();
+                       }
+                       $bytes = '';
+               }
+               if (strlen($bytes) < $count) {
+                       // PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes()
+                       // locking on Windows and rendered it unusable.
+                       if (!isset($php_compatible)) {
+                               $php_compatible = version_compare(PHP_VERSION, '5.3.4', '>=');
+                       }
+                       // /dev/urandom is available on many *nix systems and is considered the
+                       // best commonly available pseudo-random source.
+                       if ($fh = @fopen('/dev/urandom', 'rb')) {
+                               // PHP only performs buffered reads, so in reality it will always read
+                               // at least 4096 bytes. Thus, it costs nothing extra to read and store
+                               // that much so as to speed any additional invocations.
+                               $bytes .= fread($fh, max(4096, $count));
+                               fclose($fh);
+                       } elseif ($php_compatible && function_exists('openssl_random_pseudo_bytes')) {
+                               // openssl_random_pseudo_bytes() will find entropy in a system-dependent
+                               // way.
+                               $bytes .= openssl_random_pseudo_bytes($count - strlen($bytes));
+                       }
+                       // If /dev/urandom is not available or returns no bytes, this loop will
+                       // generate a good set of pseudo-random bytes on any system.
+                       // Note that it may be important that our $random_state is passed
+                       // through hash() prior to being rolled into $output, that the two hash()
+                       // invocations are different, and that the extra input into the first one -
+                       // the microtime() - is prepended rather than appended. This is to avoid
+                       // directly leaking $random_state via the $output stream, which could
+                       // allow for trivial prediction of further "random" numbers.
+                       while (strlen($bytes) < $count) {
+                               $random_state = hash('sha256', microtime() . mt_rand() . $random_state);
+                               $bytes .= hash('sha256', mt_rand() . $random_state, true);
+                       }
+               }
+               $output = substr($bytes, 0, $count);
+               $bytes = substr($bytes, $count);
+               return $output;
+       }
+
+       /**
+        * 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;
+       }
+}
index 56936f582280f8d832f1fee41b0a640201becfe1..8047914ac0429aadde1f0973920867163dbc0016 100644 (file)
@@ -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;
        }
@@ -399,6 +402,26 @@ function get_site_secret() {
        return $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.
  *
index 7f82108c02e639b83bc87c5f6f9c3f64d0674433..f36f29668a48b173623e902195208c25a3a61922 100644 (file)
@@ -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 (file)
index 0000000..b5b6147
--- /dev/null
@@ -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"));
+}
index ad4831db7fec8d7885e779e45335f01c099cd42c..4eed63d4c8e5a716a961cf953193c7c555577e14 100644 (file)
@@ -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' => "&#x2713; 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 &gt; Site Secret",
+       'upgrade:site_secret_warning:weak' => "You are strongly encouraged to regenerate your site key to improve system security. See Configure &gt; 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 (file)
index 0000000..e70ac7a
--- /dev/null
@@ -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(),
+));
index 3896ded5d052855367daed39226c0e3617a68685..c435621b266373579a502ef7b8b6a27dd341ea77 100644 (file)
@@ -1543,6 +1543,26 @@ table.mceLayout {
        margin: 0 0 1em 2em;
 }
 
+/* ***************************************
+    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
 *************************************** */
diff --git a/views/default/forms/admin/site/regenerate_secret.php b/views/default/forms/admin/site/regenerate_secret.php
new file mode 100644 (file)
index 0000000..af269b8
--- /dev/null
@@ -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>