diff --git a/admin/check.php b/admin/check.php
index 7e69a745f5..ed5c155520 100644
--- a/admin/check.php
+++ b/admin/check.php
@@ -23,7 +23,10 @@
error_reporting( E_ALL );
-$g_skip_open_db = true; # don't open the database in database_api.php
+# Load the MantisDB core in maintenance mode. This mode will assume that
+# config_inc.php hasn't been specified. Thus the database will not be opened
+# and plugins will not be loaded.
+define( 'MANTIS_MAINTENANCE_MODE', true );
/**
* MantisBT Core API's
diff --git a/admin/install.php b/admin/install.php
index 1bfd302f1d..3b2532180e 100644
--- a/admin/install.php
+++ b/admin/install.php
@@ -25,9 +25,12 @@
/** @todo put this somewhere */
@set_time_limit( 0 );
-$g_skip_open_db = true; # don't open the database in database_api.php
-define( 'MANTIS_INSTALLER', true );
-define( 'PLUGINS_DISABLED', true );
+
+# Load the MantisDB core in maintenance mode. This mode will assume that
+# config_inc.php hasn't been specified. Thus the database will not be opened
+# and plugins will not be loaded.
+define( 'MANTIS_MAINTENANCE_MODE', true );
+
@require_once( dirname( dirname( __FILE__ ) ) . DIRECTORY_SEPARATOR . 'core.php' );
require_api( 'install_helper_functions_api.php' );
$g_error_send_page_header = false; # bypass page headers in error handler
diff --git a/admin/test_langs.php b/admin/test_langs.php
index 1b5b217593..ba84aacf7e 100644
--- a/admin/test_langs.php
+++ b/admin/test_langs.php
@@ -22,7 +22,7 @@
*/
define( 'PLUGINS_DISABLED', true );
-$g_skip_lang_load = true;
+define( 'LANG_LOAD_DISABLED', true );
/**
* MantisBT Core API's
@@ -45,7 +45,6 @@
die;
}
-unset( $g_skip_lang_load ) ;
lang_push( 'english' );
access_ensure_global_level( config_get_global( 'admin_site_threshold' ) );
diff --git a/admin/upgrade_unattended.php b/admin/upgrade_unattended.php
index 1435d4249a..4c2d43acf5 100644
--- a/admin/upgrade_unattended.php
+++ b/admin/upgrade_unattended.php
@@ -23,7 +23,11 @@
@set_time_limit( 0 );
-$g_skip_open_db = true; # don't open the database in database_api.php
+# Load the MantisDB core in maintenance mode. This mode will assume that
+# config_inc.php hasn't been specified. Thus the database will not be opened
+# and plugins will not be loaded.
+define( 'MANTIS_MAINTENANCE_MODE', true );
+
/**
* MantisBT Core API's
*/
diff --git a/config_defaults_inc.php b/config_defaults_inc.php
index 163eb52d46..e296a69dae 100644
--- a/config_defaults_inc.php
+++ b/config_defaults_inc.php
@@ -272,6 +272,39 @@
*/
$g_form_security_validation = ON;
+/*****************************
+ * Security and Cryptography *
+ *****************************/
+
+/**
+ * Master salt value used for cryptographic hashing throughout MantisBT. This
+ * value must be kept secret at all costs. You must generate a unique and
+ * random salt value for each installation of MantisBT you control. The
+ * minimum length of this string must be at least 16 characters.
+ *
+ * The value you select for this salt should be a long string generated using
+ * a secure random number generator. An example for Linux systems is:
+ * cat /dev/urandom | head -c 64 | base64
+ * Note that the number of bits of entropy per byte of output from /dev/urandom
+ * is not 8. If you're particularly paranoid and don't mind waiting a long
+ * time, you could use /dev/random to get much closer to 8 bits of entropy per
+ * byte. Moving the mouse (if possible) while generating entropy via
+ * /dev/random will greatly improve the speed at which /dev/random produces
+ * entropy.
+ *
+ * WARNING: This configuration option has a profound impact on the security of
+ * your MantisBT installation. Failure to set this configuration option
+ * correctly could lead to your MantisBT installation being compromised. Ensure
+ * that this value remains secret. Treat it with the same security that you'd
+ * treat the password to your MantisDB database.
+ *
+ * This setting is blank by default. MantisBT will not operate in this state.
+ * Hence you are forced to change the value of this configuration option.
+ *
+ * @global string $g_crypto_master_salt
+ */
+$g_crypto_master_salt = '';
+
/****************************
* Signup and Lost Password *
****************************/
@@ -3975,6 +4008,7 @@
'compress_html',
'content_expire',
'cookie',
+ 'crypto_master_salt',
'custom_headers',
'database_name',
'^db_',
diff --git a/core.php b/core.php
index 35934becd4..b93377ac05 100644
--- a/core.php
+++ b/core.php
@@ -33,6 +33,7 @@
* @uses config_defaults_inc.php
* @uses config_inc.php
* @uses constant_inc.php
+ * @uses crypto_api.php
* @uses custom_constants_inc.php
* @uses custom_functions_inc.php
* @uses database_api.php
@@ -199,24 +200,25 @@ function __autoload( $className ) {
}
}
+# Initialise cryptographic keys
+require_api( 'crypto_api.php' );
+crypto_init();
+
# Connect to the database
require_api( 'database_api.php' );
require_api( 'config_api.php' );
-if( !isset( $g_skip_open_db ) ) {
+if ( !defined( 'MANTIS_MAINTENANCE_MODE' ) ) {
if( OFF == $g_use_persistent_connections ) {
db_connect( config_get_global( 'dsn', false ), $g_hostname, $g_db_username, $g_db_password, $g_database_name, config_get_global( 'db_schema' ) );
} else {
db_connect( config_get_global( 'dsn', false ), $g_hostname, $g_db_username, $g_db_password, $g_database_name, config_get_global( 'db_schema' ), true );
}
-} else {
- if (!defined('PLUGINS_DISABLED') )
- define( 'PLUGINS_DISABLED', true );
}
# Initialise plugins
-require_api( 'plugin_api.php' );
-if ( !defined( 'PLUGINS_DISABLED' ) ) {
+if ( !defined( 'PLUGINS_DISABLED' ) && !defined( 'MANTIS_MAINTENANCE_MODE' ) ) {
+ require_api( 'plugin_api.php' );
plugin_init_installed();
}
@@ -239,13 +241,13 @@ function __autoload( $className ) {
}
require_api( 'authentication_api.php' );
-require_api( 'user_pref_api.php' );
if( auth_is_user_authenticated() ) {
+ require_api( 'user_pref_api.php' );
date_default_timezone_set( user_pref_get_pref( auth_get_current_user_id(), 'timezone' ) );
}
-require_api( 'collapse_api.php' );
-if ( !defined( 'MANTIS_INSTALLER' ) ) {
+if ( !defined( 'MANTIS_MAINTENANCE_MODE' ) ) {
+ require_api( 'collapse_api.php' );
collapse_cache_token();
}
@@ -260,11 +262,13 @@ function __autoload( $className ) {
http_all_headers();
# Push default language to speed calls to lang_get
-require_api( 'lang_api.php' );
-if ( !isset( $g_skip_lang_load ) ) {
+if ( !defined( 'LANG_LOAD_DISABLED' ) ) {
+ require_api( 'lang_api.php' );
lang_push( lang_get_default() );
}
# Signal plugins that the core system is loaded
-require_api( 'event_api.php' );
-event_signal( 'EVENT_CORE_READY' );
+if ( !defined( 'PLUGINS_DISABLED' ) && !defined( 'MANTIS_MAINTENANCE_MODE' ) ) {
+ require_api( 'event_api.php' );
+ event_signal( 'EVENT_CORE_READY' );
+}
diff --git a/core/constant_inc.php b/core/constant_inc.php
index 772c7ddbdf..385a522153 100644
--- a/core/constant_inc.php
+++ b/core/constant_inc.php
@@ -413,6 +413,10 @@
# ERROR_FORM_*
define( 'ERROR_FORM_TOKEN_INVALID', 2800 );
+# ERROR_CRYPTO_*
+define( 'ERROR_CRYPTO_MASTER_SALT_INVALID', 2900 );
+define( 'ERROR_CRYPTO_CAN_NOT_GENERATE_STRONG_RANDOMNESS', 2901 );
+
# Generic position constants
define( 'POSITION_NONE', 0 );
define( 'POSITION_TOP', 1 );
diff --git a/core/crypto_api.php b/core/crypto_api.php
new file mode 100644
index 0000000000..cbc417efa6
--- /dev/null
+++ b/core/crypto_api.php
@@ -0,0 +1,145 @@
+.
+
+/**
+ * Crypto API
+ *
+ * @package CoreAPI
+ * @subpackage CryptoAPI
+ * @copyright Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
+ * @copyright Copyright (C) 2002 - 2010 MantisBT Team - mantisbt-dev@lists.sourceforge.net
+ * @link http://www.mantisbt.org
+ *
+ * @uses config_api.php
+ * @uses constant_inc.php
+ * @uses error_api.php
+ */
+
+require_api( 'config_api.php' );
+require_api( 'constant_inc.php' );
+require_api( 'error_api.php' );
+
+/**
+ * Initialise the CryptoAPI subsystem. This function checks whether the master
+ * salt is specified correctly within the configuration. If not, a fatal error
+ * is produced to protect against invalid configuration impacting the security
+ * of the MantisBT installation.
+ * @return null
+ */
+function crypto_init() {
+ if ( !defined( 'MANTIS_MAINTENANCE_MODE' ) ) {
+ if ( strlen( config_get_global( 'crypto_master_salt' ) ) < 16 ) {
+ trigger_error( ERROR_CRYPTO_MASTER_SALT_INVALID, ERROR );
+ }
+ }
+ return;
+}
+
+/**
+ * Generate a random string (raw binary output) for cryptographic purposes such
+ * as nonces, IVs, default passwords, etc. This function will attempt to
+ * generate strong randomness but can optionally be used to generate weaker
+ * randomness if less security is needed or a strong source of randomness isn't
+ * available. The use of weak randomness for cryptographic purposes is strongly
+ * discouraged because it contains low entropy and is predictable.
+ * @param int $p_bytes Number of bytes of randomness required
+ * @param bool $p_require_strong_generator Whether or not a weak source of randomness can be used by this function
+ * @return string|null Raw binary string containing the requested number of bytes of random output or null if the output couldn't be created
+ */
+function crypto_generate_random_string( $p_bytes, $p_require_strong_generator = true ) {
+
+ # First we attempt to use the secure PRNG provided by OpenSSL in PHP 5.3
+ if ( function_exists( 'openssl_random_pseudo_bytes' ) ) {
+ $t_random_bytes = openssl_random_pseudo_bytes( $p_bytes, $t_strong );
+ if ( $t_random_bytes !== false ) {
+ if ( $p_require_strong_generator && $t_strong === true ) {
+ $t_random_string = $t_random_bytes;
+ } else if ( !$p_require_strong_generator ) {
+ $t_random_string = $t_random_bytes;
+ }
+ }
+ }
+
+ # Next we try to use the /dev/urandom PRNG provided on Linux systems. This
+ # is nowhere near as secure as /dev/random but it is still satisfactory for
+ # the needs of MantisBT, especially given the fact that we don't want this
+ # function to block while waiting for the system to generate more entropy.
+ $t_urandom_fp = @fopen( '/dev/urandom', 'rb' );
+ if ( $t_urandom_fp !== false ) {
+ $t_random_bytes = @fread( $t_urandom_fp, $p_bytes );
+ if ( $t_random_bytes !== false ) {
+ $t_random_string = $t_random_bytes;
+ }
+ @fclose( $t_urandom_fp );
+ }
+
+ # For Windows systems, we can try using Microsoft CryptoAPI to retrieve
+ # more reliable PRNG output than what PHP can provide by itself.
+ # !TODO
+
+ # At this point we've run out of possibilities for generating randomness
+ # from a strong source. Unless weak output is specifically allowed by the
+ # $p_require_strong_generator argument, we should return null as we've
+ # failed to generate randomness to a satisfactory security level.
+ if ( !isset( $t_random_string ) && $p_require_strong_generator ) {
+ return null;
+ }
+
+ # As a last resort we have to fall back to using the insecure Mersenne
+ # Twister pseudo random number generator provided in PHP. This DOES NOT
+ # produce cryptographically secure randomness and thus the output of the
+ # PRNG is easily guessable. In an attempt to make it harder to guess the
+ # internal state of the PRNG, we salt the PRNG output with a known secret
+ # and hash it.
+ if ( !isset( $t_random_string ) ) {
+ $t_secret_key = 'prng' . config_get_global( 'crypto_master_salt' );
+ $t_random_bytes = '';
+ for ( $i = 0; $i < $p_bytes; $i += 64 ) {
+ $t_random_segment = '';
+ for ( $j = 0; $j < 64; $j++ ) {
+ $t_random_segment .= base_convert( mt_rand(), 10, 36 );
+ }
+ $t_random_segment .= $i;
+ $t_random_segment .= $t_secret_key;
+ $t_random_bytes .= hash( 'whirlpool', $t_random_segment, true );
+ }
+ $t_random_string = substr( $t_random_bytes, 0, $p_bytes );
+ if ( $t_random_string === false ) {
+ return null; # Unexpected error
+ }
+ }
+
+ return $t_random_string;
+}
+
+/**
+ * Generate a strong random string (raw binary output) for cryptographic
+ * purposes such as nonces, IVs, default passwords, etc. If a strong source
+ * of randomness is not available, this function will fail and produce an
+ * error. Strong randomness is different from weak randomness in that a strong
+ * randomness generator doesn't produce predictable output and has much higher
+ * entropy. Where randomness is being used for cryptographic purposes, a strong
+ * source of randomness should always be used.
+ * @param int $p_bytes Number of bytes of strong randomness required
+ * @return string Raw binary string containing the requested number of bytes of random output
+ */
+function crypto_generate_strong_random_string( $p_bytes ) {
+ $t_random_string = crypto_generate_random_string( $p_bytes, true );
+ if ( $t_random_string === null ) {
+ trigger_error( ERROR_CRYPTO_CAN_NOT_GENERATE_STRONG_RANDOMNESS, ERROR );
+ }
+ return $t_random_string;
+}
diff --git a/docbook/adminguide/en/configuration.sgml b/docbook/adminguide/en/configuration.sgml
index 80fe70767d..cdda7cfdc1 100644
--- a/docbook/adminguide/en/configuration.sgml
+++ b/docbook/adminguide/en/configuration.sgml
@@ -161,6 +161,54 @@
+
+ Security and Cryptography
+
+
+
+ $g_crypto_master_salt
+
+ Master salt value used for cryptographic hashing
+ throughout MantisBT. This value must be kept secret at
+ all costs. You must generate a unique and random salt
+ value for each installation of MantisBT you control.
+ The minimum length of this string must be at least 16
+ characters.
+
+ The value you select for this salt should be a long
+ string generated using a secure random number
+ generator. An example for Linux systems is:
+
+ cat /dev/urandom | head -c 64 | base64
+ Note that the number of bits of entropy per byte of
+ output from /dev/urandom is not 8. If you're
+ particularly paranoid and don't mind waiting a long
+ time, you could use /dev/random to get much closer to
+ 8 bits of entropy per byte. Moving the mouse
+ (if possible) while generating entropy via /dev/random
+ will greatly improve the speed at which /dev/random
+ produces entropy.
+
+ This setting is blank by default. MantisBT will not
+ operate in this state. Hence you are forced to change
+ the value of this configuration option.
+
+
+ WARNING: This configuration option has a profound
+ impact on the security of your MantisBT
+ installation. Failure to set this configuration
+ option correctly could lead to your MantisBT
+ installation being compromised. Ensure that this
+ value remains secret. Treat it with the same
+ security that you'd treat the password to your
+ MantisDB database.
+
+
+
+
+
+
+
Signup and Lost Password
diff --git a/lang/strings_english.txt b/lang/strings_english.txt
index 051671a4f0..a5b3ce6e4b 100644
--- a/lang/strings_english.txt
+++ b/lang/strings_english.txt
@@ -296,6 +296,8 @@ $MANTIS_ERROR[ERROR_SESSION_HANDLER_INVALID] = 'Invalid session handler.';
$MANTIS_ERROR[ERROR_SESSION_VAR_NOT_FOUND] = 'Session variable "%1$s" not found.';
$MANTIS_ERROR[ERROR_SESSION_NOT_VALID] = 'Your session has become invalidated.';
$MANTIS_ERROR[ERROR_FORM_TOKEN_INVALID] = 'Invalid form security token. Did you submit the form twice by accident?';
+$MANTIS_ERROR[ERROR_CRYPTO_MASTER_SALT_INVALID] = 'For security reasons MantisBT will not operate when $g_crypto_master_salt is not specified correctly in config_inc.php.';
+$MANTIS_ERROR[ERROR_CRYPTO_CAN_NOT_GENERATE_STRONG_RANDOMNESS] = 'Unable to find a source of strong randomness for cryptographic purposes.';
$MANTIS_ERROR[ERROR_INVALID_REQUEST_METHOD] = 'This page cannot be accessed using this method.';
$MANTIS_ERROR[ERROR_INVALID_SORT_FIELD] = 'Invalid sort field.';
$MANTIS_ERROR[ERROR_INVALID_DATE_FORMAT] = 'Invalid date format.';