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.';