Merge branch 'pliablepixels-crypt-replacement3' into storageareas

This commit is contained in:
Isaac Connor 2019-05-23 15:21:58 -04:00
commit 4466ef13fd
3 changed files with 64 additions and 68 deletions

2
.gitmodules vendored
View File

@ -7,7 +7,7 @@
url = https://github.com/ZoneMinder/CakePHP-Enum-Behavior.git
[submodule "third_party/bcrypt"]
path = third_party/bcrypt
url = https://github.com/pliablepixels/libbcrypt
url = https://github.com/ZoneMinder/libbcrypt
[submodule "third_party/jwt-cpp"]
path = third_party/jwt-cpp
url = https://github.com/Thalhammer/jwt-cpp

View File

@ -112,21 +112,23 @@ sub interpret_messages {
my @results;
foreach my $response ( @responses ) {
if($verbose) {
if ( $verbose ) {
print "Received message:\n" . $response . "\n";
}
my $result = deserialize_message($svc_discover, $response);
if(not $result) {
if ( not $result ) {
print "Error deserializing message. No message returned from deserializer.\n" if $verbose;
next;
}
my $xaddr;
foreach my $l_xaddr (split ' ', $result->get_ProbeMatch()->get_XAddrs()) {
my $probe_match = $result->get_ProbeMatch();
next if ! $probe_match;
foreach my $l_xaddr (split ' ', $probe_match->get_XAddrs()) {
# find IPv4 address
print "l_xaddr = $l_xaddr\n" if $verbose;
if($l_xaddr =~ m|//[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[:/]|) {
if ( $l_xaddr =~ m|//[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[:/]| ) {
$xaddr = $l_xaddr;
last;
} else {

View File

@ -26,25 +26,23 @@ use \Firebase\JWT\JWT;
// this function migrates mysql hashing to bcrypt, if you are using PHP >= 5.5
// will be called after successful login, only if mysql hashing is detected
function migrateHash($user, $pass) {
if (function_exists('password_hash')) {
ZM\Info ("Migrating $user to bcrypt scheme");
if ( function_exists('password_hash') ) {
ZM\Info("Migrating $user to bcrypt scheme");
// let it generate its own salt, and ensure bcrypt as PASSWORD_DEFAULT may change later
// we can modify this later to support argon2 etc as switch to its own password signature detection
$bcrypt_hash = password_hash($pass, PASSWORD_BCRYPT);
//ZM\Info ("hased bcrypt $pass is $bcrypt_hash");
$update_password_sql = 'UPDATE Users SET Password=\''.$bcrypt_hash.'\' WHERE Username=\''.$user.'\'';
ZM\Info ($update_password_sql);
ZM\Info($update_password_sql);
dbQuery($update_password_sql);
}
else {
ZM\Info ('Cannot migrate password scheme to bcrypt, as you are using PHP < 5.3');
# Since password field has changed, existing auth_hash is no longer valid
generateAuthHash(ZM_AUTH_HASH_IPS, true);
} else {
ZM\Info('Cannot migrate password scheme to bcrypt, as you are using PHP < 5.3');
return;
}
}
// core function used to login a user to PHP. Is also used for cake sessions for the API
function userLogin($username='', $password='', $passwordHashed=false, $apiLogin = false) {
@ -58,7 +56,7 @@ function userLogin($username='', $password='', $passwordHashed=false, $apiLogin
// if true, a popup will display after login
// lets validate reCaptcha if it exists
// this only applies if it userLogin was not called from API layer
if (!$apiLogin
if ( !$apiLogin
&& defined('ZM_OPT_USE_GOOG_RECAPTCHA')
&& defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY')
&& defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY')
@ -73,17 +71,17 @@ function userLogin($username='', $password='', $passwordHashed=false, $apiLogin
'remoteip' => $_SERVER['REMOTE_ADDR']
);
$res = do_post_request($url, http_build_query($fields));
$responseData = json_decode($res,true);
$responseData = json_decode($res, true);
// credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php
// if recaptcha resulted in error, we might have to deny login
if ( isset($responseData['success']) && $responseData['success'] == false ) {
if ( isset($responseData['success']) && ($responseData['success'] == false) ) {
// PP - before we deny auth, let's make sure the error was not 'invalid secret'
// because that means the user did not configure the secret key correctly
// in this case, we prefer to let him login in and display a message to correct
// the key. Unfortunately, there is no way to check for invalid site key in code
// as it produces the same error as when you don't answer a recaptcha
if ( isset($responseData['error-codes']) && is_array($responseData['error-codes']) ) {
if ( !in_array('invalid-input-secret',$responseData['error-codes']) ) {
if ( !in_array('invalid-input-secret', $responseData['error-codes']) ) {
Error('reCaptcha authentication failed');
return null;
} else {
@ -102,18 +100,18 @@ function userLogin($username='', $password='', $passwordHashed=false, $apiLogin
// First retrieve the stored password
// and move password hashing to application space
$saved_user_details = dbFetchOne ($sql, NULL, $sql_values);
$saved_user_details = dbFetchOne($sql, NULL, $sql_values);
$password_correct = false;
$password_type = NULL;
if ($saved_user_details) {
if ( $saved_user_details ) {
// if the API layer asked us to login, make sure the user
// has API enabled (admin may have banned API for this user)
if ($apiLogin) {
if ($saved_user_details['APIEnabled'] != 1) {
ZM\Error ("API disabled for: $username");
if ( $apiLogin ) {
if ( $saved_user_details['APIEnabled'] != 1 ) {
ZM\Error("API disabled for: $username");
$_SESSION['loginFailed'] = true;
unset($user);
return false;
@ -121,45 +119,39 @@ function userLogin($username='', $password='', $passwordHashed=false, $apiLogin
}
$saved_password = $saved_user_details['Password'];
if ($saved_password[0] == '*') {
if ( $saved_password[0] == '*' ) {
// We assume we don't need to support mysql < 4.1
// Starting MY SQL 4.1, mysql concats a '*' in front of its password hash
// https://blog.pythian.com/hashing-algorithm-in-mysql-password-2/
ZM\Logger::Debug ('Saved password is using MYSQL password function');
$input_password_hash ='*'.strtoupper(sha1(sha1($password, true)));
ZM\Logger::Debug('Saved password is using MYSQL password function');
$input_password_hash = '*'.strtoupper(sha1(sha1($password, true)));
$password_correct = ($saved_password == $input_password_hash);
$password_type = 'mysql';
}
elseif (preg_match('/^\$2[ayb]\$.+$/', $saved_password)) {
} else if ( preg_match('/^\$2[ayb]\$.+$/', $saved_password) ) {
ZM\Logger::Debug('bcrypt signature found, assumed bcrypt password');
$password_type='bcrypt';
$password_correct = $passwordHashed? ($password == $saved_password) : password_verify($password, $saved_password);
$password_type = 'bcrypt';
$password_correct = $passwordHashed ? ($password == $saved_password) : password_verify($password, $saved_password);
}
// zmupdate.pl adds a '-ZM-' prefix to overlay encrypted passwords
// this is done so that we don't spend cycles doing two bcrypt password_verify calls
// for every wrong password entered. This will only be invoked for passwords zmupdate.pl has
// overlay hashed
elseif (substr($saved_password, 0,4) == '-ZM-') {
else if ( substr($saved_password, 0,4) == '-ZM-' ) {
ZM\Logger::Debug("Detected bcrypt overlay hashing for $username");
ZM\Info("Detected bcrypt overlay hashing for $username");
$bcrypt_hash = substr ($saved_password, 4);
$mysql_encoded_password ='*'.strtoupper(sha1(sha1($password, true)));
$bcrypt_hash = substr($saved_password, 4);
$mysql_encoded_password = '*'.strtoupper(sha1(sha1($password, true)));
ZM\Logger::Debug("Comparing password $mysql_encoded_password to bcrypt hash: $bcrypt_hash");
ZM\Info("Comparing password $mysql_encoded_password to bcrypt hash: $bcrypt_hash");
$password_correct = password_verify($mysql_encoded_password, $bcrypt_hash);
$password_type = "mysql"; // so we can migrate later down
}
else {
$password_type = 'mysql'; // so we can migrate later down
} else {
// we really should nag the user not to use plain
ZM\Warning ('assuming plain text password as signature is not known. Please do not use plain, it is very insecure');
$password_type = 'plain';
$password_correct = ($saved_password == $password);
}
} else {
ZM\Error ("Could not retrieve user $username details");
ZM\Error("Could not retrieve user $username details");
$_SESSION['loginFailed'] = true;
unset($user);
return false;
@ -172,10 +164,10 @@ function userLogin($username='', $password='', $passwordHashed=false, $apiLogin
}
$_SESSION['remoteAddr'] = $_SERVER['REMOTE_ADDR']; // To help prevent session hijacking
if ($password_correct) {
if ( $password_correct ) {
ZM\Info("Login successful for user \"$username\"");
$user = $saved_user_details;
if ($password_type == 'mysql') {
if ( $password_type == 'mysql' ) {
ZM\Info('Migrating password, if possible for future logins');
migrateHash($username, $password);
}
@ -224,42 +216,41 @@ function validateToken ($token, $allowed_token_type='access') {
$jwt_payload = json_decode(json_encode($decoded_token), true);
$type = $jwt_payload['type'];
if ($type != $allowed_token_type) {
if ($allowed_token_type == 'access') {
if ( $type != $allowed_token_type ) {
if ( $allowed_token_type == 'access' ) {
// give a hint that the user is not doing it right
ZM\Error ('Please do not use refresh tokens for this operation');
ZM\Error('Please do not use refresh tokens for this operation');
}
return array (false, "Incorrect token type");
return array (false, 'Incorrect token type');
}
$username = $jwt_payload['user'];
$sql = 'SELECT * FROM Users WHERE Enabled=1 AND Username = ?';
$sql_values = array($username);
$saved_user_details = dbFetchOne ($sql, NULL, $sql_values);
$saved_user_details = dbFetchOne($sql, NULL, $sql_values);
if ($saved_user_details) {
if ( $saved_user_details ) {
$issuedAt = $jwt_payload['iat'];
$minIssuedAt = $saved_user_details['TokenMinExpiry'];
if ($issuedAt < $minIssuedAt) {
ZM\Error ("Token revoked for \"$username\". Please generate a new token");
if ( $issuedAt < $minIssuedAt ) {
ZM\Error("Token revoked for $username. Please generate a new token");
$_SESSION['loginFailed'] = true;
unset($user);
return array(false, "Token revoked. Please re-generate");
return array(false, 'Token revoked. Please re-generate');
}
$user = $saved_user_details;
return array($user, "OK");
return array($user, 'OK');
} else {
ZM\Error ("Could not retrieve user \"$username\" details");
ZM\Error("Could not retrieve user $username details");
$_SESSION['loginFailed'] = true;
unset($user);
return array(false, "No such user/credentials");
return array(false, 'No such user/credentials');
}
}
} // end function validateToken($token, $allowed_token_type='access')
function getAuthUser($auth) {
if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' && !empty($auth) ) {
@ -299,8 +290,6 @@ function getAuthUser($auth) {
return false;
} // end getAuthUser($auth)
function generateAuthHash($useRemoteAddr, $force=false) {
if ( ZM_OPT_USE_AUTH and ZM_AUTH_RELAY == 'hashed' and isset($_SESSION['username']) and $_SESSION['passwordHash'] ) {
$time = time();
@ -367,10 +356,16 @@ if ( ZM_OPT_USE_AUTH ) {
}
if ( isset($_SESSION['username']) ) {
if ( ZM_AUTH_HASH_LOGINS ) {
# Extra validation, if logged in, then the auth hash will be set in the session, so we can validate it.
# This prevent session modification to switch users
$user = getAuthUser($_SESSION['AuthHash'.$_SESSION['remoteAddr']);
} else {
# Need to refresh permissions and validate that the user still exists
$sql = 'SELECT * FROM Users WHERE Enabled=1 AND Username=?';
$user = dbFetchOne($sql, NULL, array($_SESSION['username']));
}
}
if ( ZM_AUTH_RELAY == 'plain' ) {
// Need to save this in session
@ -379,7 +374,6 @@ if ( ZM_OPT_USE_AUTH ) {
$_SESSION['remoteAddr'] = $_SERVER['REMOTE_ADDR']; // To help prevent session hijacking
if ( ZM_AUTH_HASH_LOGINS && empty($user) && !empty($_REQUEST['auth']) ) {
if ( $authUser = getAuthUser($_REQUEST['auth']) ) {
userLogin($authUser['Username'], $authUser['Password'], true);
}
@ -387,7 +381,7 @@ if ( ZM_OPT_USE_AUTH ) {
userLogin($_REQUEST['username'], $_REQUEST['password'], false);
}
if ( ZM_AUTH_HASH_LOGINS && empty($user) && !empty($_REQUEST['token']) ) {
if (empty($user) && !empty($_REQUEST['token']) ) {
$ret = validateToken($_REQUEST['token'], 'access');
$user = $ret[0];