From 483b6238a3599595a678f995b2c7c9e9f07a7ce7 Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Tue, 30 Jul 2013 18:46:02 +0200 Subject: Add truly random numbers and use them in places where randomness matters --- inc/auth.php | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 6 deletions(-) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index 537d44c01..ace98f51f 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -294,7 +294,7 @@ function auth_validateToken($token) { * @return string The auth token */ function auth_createToken() { - $token = md5(mt_rand()); + $token = md5(auth_randombytes(16)); @session_start(); // reopen the session if needed $_SESSION[DOKU_COOKIE]['auth']['token'] = $token; session_write_close(); @@ -349,6 +349,102 @@ function auth_cookiesalt($addsession = false) { return $salt; } +/** + * Return truly (pseudo) random bytes if available, otherwise fall back to mt_rand + * + * @author Mark Seecof + * @author Michael Hamann + * @link http://www.php.net/manual/de/function.mt-rand.php#83655 + * @param int $length number of bytes to get + * @throws Exception when no usable random generator is found + * @return string binary random strings + */ +function auth_randombytes($length) { + $strong = false; + $rbytes = false; + + if (function_exists('openssl_random_pseudo_bytes') + && (version_compare(PHP_VERSION, '5.3.4') >= 0 + || strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') + ) { + $rbytes = openssl_random_pseudo_bytes($length, $strong); + } + + if (!$strong && function_exists('mcrypt_create_iv') + && (version_compare(PHP_VERSION, '5.3.7') >= 0 + || strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') + ) { + $rbytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + if ($rbytes !== false && strlen($rbytes) === $length) { + $strong = true; + } + } + + + // If no strong randoms available, try OS the specific ways + if(!$strong) { + // Unix/Linux platform + $fp = @fopen('/dev/urandom', 'rb'); + if($fp !== false) { + $rbytes = fread($fp, $length); + fclose($fp); + } + + // MS-Windows platform + if(class_exists('COM')) { + // http://msdn.microsoft.com/en-us/library/aa388176(VS.85).aspx + try { + $CAPI_Util = new COM('CAPICOM.Utilities.1'); + $rbytes = $CAPI_Util->GetRandom($length, 0); + + // if we ask for binary data PHP munges it, so we + // request base64 return value. + if($rbytes) $rbytes = base64_decode($rbytes); + } catch(Exception $ex) { + // fail + } + } + } + if(strlen($rbytes) < $length) $rbytes = false; + + // still no random bytes available - fall back to mt_rand() + if($rbytes === false) { + $rbytes = ''; + for ($i = 0; $i < $length; ++$i) { + $rbytes .= chr(mt_rand(0, 255)); + } + } + + return $rbytes; +} + +/** + * Random number generator using the best available source + * + * @author Michael Samuel + * @author Michael Hamann + * @param int $min + * @param int $max + * @return int + */ +function auth_random($min, $max) { + $abs_max = $max - $min; + + $nbits = 0; + for ($n = $abs_max; $n > 0; $n >>= 1) { + ++$nbits; + } + + $mask = (1 << $nbits) - 1; + do { + $bytes = auth_randombytes(PHP_INT_SIZE); + $integers = unpack('Inum', $bytes); + $integer = $integers["num"] & $mask; + } while ($integer > $abs_max); + + return $min + $integer; +} + /** * Log out the current user * @@ -703,12 +799,12 @@ function auth_pwgen($foruser = '') { //use thre syllables... for($i = 0; $i < 3; $i++) { - $data['password'] .= $c[mt_rand(0, strlen($c) - 1)]; - $data['password'] .= $v[mt_rand(0, strlen($v) - 1)]; - $data['password'] .= $a[mt_rand(0, strlen($a) - 1)]; + $data['password'] .= $c[auth_random(0, strlen($c) - 1)]; + $data['password'] .= $v[auth_random(0, strlen($v) - 1)]; + $data['password'] .= $a[auth_random(0, strlen($a) - 1)]; } //... and add a nice number and special - $data['password'] .= mt_rand(10, 99).$s[mt_rand(0, strlen($s) - 1)]; + $data['password'] .= auth_random(10, 99).$s[auth_random(0, strlen($s) - 1)]; } $evt->advise_after(); @@ -1007,7 +1103,7 @@ function act_resendpwd() { } // generate auth token - $token = md5(uniqid(mt_rand(), true)); // random secret + $token = md5(auth_randombytes(16)); // random secret $tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth'; $url = wl('', array('do'=> 'resendpwd', 'pwauth'=> $token), true, '&'); -- cgit v1.2.3 From 27058a053ea6ffa90fee7aaf26f81ab1109d6df3 Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Tue, 30 Jul 2013 13:40:59 +0200 Subject: Fix and add type declarations for the auth system --- inc/auth.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index 47b29eff7..537d44c01 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -40,7 +40,7 @@ function auth_setup() { global $INPUT; global $AUTH_ACL; global $lang; - global $config_cascade; + /* @var Doku_Plugin_Controller $plugin_controller */ global $plugin_controller; $AUTH_ACL = array(); @@ -207,7 +207,7 @@ function auth_login($user, $pass, $sticky = false, $silent = false) { global $USERINFO; global $conf; global $lang; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; $sticky ? $sticky = true : $sticky = false; //sanity check @@ -361,7 +361,7 @@ function auth_cookiesalt($addsession = false) { function auth_logoff($keepbc = false) { global $conf; global $USERINFO; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; // make sure the session is writable (it usually is) @@ -407,7 +407,7 @@ function auth_logoff($keepbc = false) { function auth_ismanager($user = null, $groups = null, $adminonly = false) { global $conf; global $USERINFO; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; if(!$auth) return false; @@ -460,7 +460,7 @@ function auth_isadmin($user = null, $groups = null) { * @return bool true for membership acknowledged */ function auth_isMember($memberlist, $user, array $groups) { - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; if(!$auth) return false; @@ -526,7 +526,7 @@ function auth_quickaclcheck($id) { function auth_aclcheck($id, $user, $groups) { global $conf; global $AUTH_ACL; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; // if no ACL is used always return upload rights @@ -725,7 +725,7 @@ function auth_pwgen($foruser = '') { */ function auth_sendPassword($user, $password) { global $lang; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; if(!$auth) return false; @@ -759,7 +759,7 @@ function auth_sendPassword($user, $password) { function register() { global $lang; global $conf; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; global $INPUT; @@ -828,7 +828,7 @@ function register() { function updateprofile() { global $conf; global $lang; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; @@ -918,7 +918,7 @@ function updateprofile() { function act_resendpwd() { global $lang; global $conf; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; @@ -1084,7 +1084,7 @@ function auth_verifyPassword($clear, $crypt) { */ function auth_setCookie($user, $pass, $sticky) { global $conf; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; global $USERINFO; -- cgit v1.2.3 From 30d544a4c371bf69023e4d9958bc2b00d84387d9 Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Tue, 30 Jul 2013 18:47:58 +0200 Subject: Use a new, truly random secret for cookie encryption --- inc/auth.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index ace98f51f..a1da971ae 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -219,7 +219,7 @@ function auth_login($user, $pass, $sticky = false, $silent = false) { if($auth->checkPass($user, $pass)) { // make logininfo globally available $_SERVER['REMOTE_USER'] = $user; - $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session + $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session auth_setCookie($user, PMA_blowfish_encrypt($pass, $secret), $sticky); return true; } else { @@ -250,7 +250,7 @@ function auth_login($user, $pass, $sticky = false, $silent = false) { return true; } // no we don't trust it yet - recheck pass but silent - $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session + $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session $pass = PMA_blowfish_decrypt($pass, $secret); return auth_login($user, $pass, $sticky, true); } @@ -333,14 +333,18 @@ function auth_browseruid() { * * @author Andreas Gohr * @param bool $addsession if true, the sessionid is added to the salt + * @param bool $secure if security is more important than keeping the old value * @return string */ -function auth_cookiesalt($addsession = false) { +function auth_cookiesalt($addsession = false, $secure = false) { global $conf; $file = $conf['metadir'].'/_htcookiesalt'; + if ($secure || !file_exists($file)) { + $file = $conf['metadir'].'/_htcookiesalt2'; + } $salt = io_readFile($file); if(empty($salt)) { - $salt = uniqid(rand(), true); + $salt = bin2hex(auth_randombytes(64)); io_saveFile($file, $salt); } if($addsession) { @@ -988,7 +992,7 @@ function updateprofile() { // update cookie and session with the changed data if($changes['pass']) { list( /*user*/, $sticky, /*pass*/) = auth_getCookie(); - $pass = PMA_blowfish_encrypt($changes['pass'], auth_cookiesalt(!$sticky)); + $pass = PMA_blowfish_encrypt($changes['pass'], auth_cookiesalt(!$sticky, true)); auth_setCookie($_SERVER['REMOTE_USER'], $pass, (bool) $sticky); } return true; -- cgit v1.2.3 From 04369c3eae728e14962c41d1ab259f9e7ed99144 Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Tue, 30 Jul 2013 18:50:28 +0200 Subject: Add AES from phpseclib and use it for cookie encryption This replaces the deprecated and broken Blowfish implementation that has previously been used and should provide a lot more security. --- inc/auth.php | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index a1da971ae..f02bfebca 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -220,7 +220,7 @@ function auth_login($user, $pass, $sticky = false, $silent = false) { // make logininfo globally available $_SERVER['REMOTE_USER'] = $user; $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session - auth_setCookie($user, PMA_blowfish_encrypt($pass, $secret), $sticky); + auth_setCookie($user, auth_encrypt($pass, $secret), $sticky); return true; } else { //invalid credentials - log off @@ -251,7 +251,7 @@ function auth_login($user, $pass, $sticky = false, $silent = false) { } // no we don't trust it yet - recheck pass but silent $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session - $pass = PMA_blowfish_decrypt($pass, $secret); + $pass = auth_decrypt($pass, $secret); return auth_login($user, $pass, $sticky, true); } } @@ -449,6 +449,40 @@ function auth_random($min, $max) { return $min + $integer; } +/** + * Encrypt data using the given secret using AES + * + * The mode is CBC with a random initialization vector, the key is derived + * using pbkdf2. + * + * @param string $data The data that shall be encrypted + * @param string $secret The secret/password that shall be used + * @return string The ciphertext + */ +function auth_encrypt($data, $secret) { + $iv = auth_randombytes(16); + $cipher = new Crypt_AES(); + $cipher->setPassword($secret); + + return $cipher->encrypt($iv.$data); +} + +/** + * Decrypt the given AES ciphertext + * + * The mode is CBC, the key is derived using pbkdf2 + * + * @param string $ciphertext The encrypted data + * @param string $secret The secret/password that shall be used + * @return string The decrypted data + */ +function auth_decrypt($ciphertext, $secret) { + $cipher = new Crypt_AES(); + $cipher->setPassword($secret); + + return substr($cipher->decrypt($ciphertext), 16); +} + /** * Log out the current user * @@ -992,7 +1026,7 @@ function updateprofile() { // update cookie and session with the changed data if($changes['pass']) { list( /*user*/, $sticky, /*pass*/) = auth_getCookie(); - $pass = PMA_blowfish_encrypt($changes['pass'], auth_cookiesalt(!$sticky, true)); + $pass = auth_encrypt($changes['pass'], auth_cookiesalt(!$sticky, true)); auth_setCookie($_SERVER['REMOTE_USER'], $pass, (bool) $sticky); } return true; -- cgit v1.2.3 From 8269996a43469c1ce5295a22248ad9a9ab34efc8 Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Wed, 31 Jul 2013 11:56:16 +0200 Subject: auth_random: remove exception comment as there is no exception --- inc/auth.php | 1 - 1 file changed, 1 deletion(-) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index f02bfebca..227ee80fd 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -360,7 +360,6 @@ function auth_cookiesalt($addsession = false, $secure = false) { * @author Michael Hamann * @link http://www.php.net/manual/de/function.mt-rand.php#83655 * @param int $length number of bytes to get - * @throws Exception when no usable random generator is found * @return string binary random strings */ function auth_randombytes($length) { -- cgit v1.2.3 From 7b650cef79bb603087a8ef43b22a1f7c3d86b7ef Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Wed, 31 Jul 2013 11:56:58 +0200 Subject: auth_en/decrypt: Add explanation and more efficient decryption Added an explanation that what we do is like normal CBC but that we additionally encrypt the IV which is actually suggested by the NIST for non-random (but unique) IVs. In the decryption process it's not necessary to decrypt the IV, this should save some time. --- inc/auth.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index 227ee80fd..96b80e19e 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -459,10 +459,16 @@ function auth_random($min, $max) { * @return string The ciphertext */ function auth_encrypt($data, $secret) { - $iv = auth_randombytes(16); + $iv = auth_randombytes(16); $cipher = new Crypt_AES(); $cipher->setPassword($secret); + /* + this uses the encrypted IV as IV as suggested in + http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf, Appendix C + for unique but necessarily random IVs. The resulting ciphertext is + compatible to ciphertext that was created using a "normal" IV. + */ return $cipher->encrypt($iv.$data); } @@ -476,10 +482,12 @@ function auth_encrypt($data, $secret) { * @return string The decrypted data */ function auth_decrypt($ciphertext, $secret) { + $iv = substr($ciphertext, 0, 16); $cipher = new Crypt_AES(); $cipher->setPassword($secret); + $cipher->setIV($iv); - return substr($cipher->decrypt($ciphertext), 16); + return $cipher->decrypt(substr($ciphertext, 16)); } /** -- cgit v1.2.3 From 2a7abf2d7fee6a2d6418e5ad4b79e37e6049bd92 Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Wed, 31 Jul 2013 18:14:26 +0200 Subject: FS#2751 - self deletion of user account --- inc/auth.php | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index 537d44c01..75ba9a9ba 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -901,6 +901,45 @@ function updateprofile() { return false; } +function auth_deleteprofile(){ + global $conf; + global $lang; + /* @var auth_basic $auth */ + global $auth; + /* @var Input $INPUT */ + global $INPUT; + + if(!$INPUT->post->bool('delete')) return false; + if(!checkSecurityToken()) return false; + + // action prevented or auth module disallows + if(!actionOK('profile_delete') || !$auth->canDo('delUser')) { + msg($lang['profnodelete'], -1); + return false; + } + + if(!$INPUT->post->bool('confirm_delete')){ + msg($lang['profconfdeletemissing'], -1); + return false; + } + + if($conf['profileconfirm']) { + if(!$auth->checkPass($_SERVER['REMOTE_USER'], $INPUT->post->str('oldpass'))) { + msg($lang['badpassconfirm'], -1); + return false; + } + } + + $deleted[] = $_SERVER['REMOTE_USER']; + if($result = $auth->triggerUserMod('delete', array($deleted))) { + // force and immediate logout including removing the sticky cookie + auth_logoff(); + return true; + } + + return false; +} + /** * Send a new password * -- cgit v1.2.3 From 71422fc898ea54876cd58c3bf5c4c0d9de032b52 Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Wed, 31 Jul 2013 18:41:02 +0200 Subject: Change error message shown for incorrect current password on update profile form. The current message confusingly mentions bad 'username' when username is not involved. The new message is the same as that introduced for an incorrect current password on the self delete profile form (FS#2751) --- inc/auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index 537d44c01..29ef46d03 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -883,7 +883,7 @@ function updateprofile() { if($conf['profileconfirm']) { if(!$auth->checkPass($_SERVER['REMOTE_USER'], $INPUT->post->str('oldpass'))) { - msg($lang['badlogin'], -1); + msg($lang['badpassconfirm'], -1); return false; } } -- cgit v1.2.3 From 73012efd9607b31a4ddd7856761cd1dac5774eef Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Fri, 2 Aug 2013 17:57:07 +0200 Subject: coding corrections. correct type hint, remove unused variable assignment --- inc/auth.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index 75ba9a9ba..a9d53779c 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -904,7 +904,7 @@ function updateprofile() { function auth_deleteprofile(){ global $conf; global $lang; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; @@ -931,7 +931,7 @@ function auth_deleteprofile(){ } $deleted[] = $_SERVER['REMOTE_USER']; - if($result = $auth->triggerUserMod('delete', array($deleted))) { + if($auth->triggerUserMod('delete', array($deleted))) { // force and immediate logout including removing the sticky cookie auth_logoff(); return true; -- cgit v1.2.3