From e9c6208b549654f0da5f9d2be3822f4ef65c60ef Mon Sep 17 00:00:00 2001 From: Gregory Soutade Date: Mon, 9 Feb 2015 18:57:49 +0100 Subject: [PATCH] Introduce shadow logins --- server/_user | 2 +- server/conf.php | 22 +++++ server/functions.php | 198 ++++++++++++++++++++++--------------- server/index.php | 58 +++++++---- server/init.sql | 4 +- server/ref/gpass.bdd | Bin 3072 -> 4096 bytes server/resources/gpass.css | 2 +- server/resources/gpass.js | 163 +++++++++++++++++++++++------- 8 files changed, 316 insertions(+), 133 deletions(-) mode change 100755 => 100644 server/ref/gpass.bdd diff --git a/server/_user b/server/_user index ba87b83..65e38db 100644 --- a/server/_user +++ b/server/_user @@ -47,7 +47,7 @@ if ($PKDBF2_LEVEL != 1000) for ($i=0; isset($_POST["k$i"]); $i++) { - $statement->bindValue(":login", $_POST["k$i"]); + $statement->bindValue(":login", addslashes($_POST["k$i"])); $result = $statement->execute(); $row = $result->fetchArray(SQLITE3_ASSOC); $result->finalize(); diff --git a/server/conf.php b/server/conf.php index 2cfa5aa..28c005c 100644 --- a/server/conf.php +++ b/server/conf.php @@ -38,4 +38,26 @@ $ADMIN_MODE=true; this value with existings masterkeys, they will unusable ! */ $PKDBF2_LEVEL=1000; + +/* + This is a security feature : It protects from database dump + and database purge without authentication. + When get all entries, instead of returning logins/passwords, + it returns "shadow logins". These are random values. + Shadow logins must be encrypted using masterkey and salt + (to generate a unique PKDBF2 derivation) that result in an access tokens. + With this access token, user has the right to get + encrypted login/password values and remove them. + It's a kind of challenge. + + This option is backward compatible with old version < 0.6, but + once activated it cannot be reverted as access tokens will be + generated for all values. So, if you want to test it, make + a copy of your databases before ! + + For now it's deactivated because it requires high cpu bandwidth + (one derivation + two decryption for each password !). When + standard crypto API will be stable it will be enabled by default. +*/ +$USE_SHADOW_LOGINS=0; ?> \ No newline at end of file diff --git a/server/functions.php b/server/functions.php index 690050d..3612e08 100755 --- a/server/functions.php +++ b/server/functions.php @@ -28,72 +28,11 @@ */ $MAX_ENTRY_LEN = 512; $USERS_PATH = "./users/"; +$TARGET_DB_VERSION = 1; -function open_crypto($mkey) +function sanitize($val) { - if (!isset($_SESSION['td'])) - { - $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, ''); - - if ($td == false) - die("Unable to open mcrypt"); - - $ret = mcrypt_generic_init($td, hex2bin($mkey), '0000000000000000'); - - if ($ret < 0) - { - echo "
Unable to set key $ret
"; - return null; - } - - $_SESSION['td'] = $td; - } - else - $td = $_SESSION['td']; - - return $td; -} - -function decrypt($mkey, $val, $salted) -{ - $td = open_crypto($mkey); - - if ($td == null) return; - - $val = mdecrypt_generic($td, hex2bin($val)); - - // Remove 0 added by encrypt - $val = str_replace("\0", '', $val); - - // Remove salt - if ($salted) - $val = substr($val, 0, strlen($val)-3); - - return $val; -} - -function encrypt($mkey, $val, $salted) -{ - global $MAX_ENTRY_LEN; - - $td = open_crypto($mkey); - - if ($td == null) return; - - if ($salted) - { - $val .= dechex(rand(256,4095)); //between 0x100 and 0xfff - } - - $val = mcrypt_generic($td, $val); - - if (strlen($val) > $MAX_ENTRY_LEN) - { - echo "
Value to encrypt is too long
"; - return null; - } - - return bin2hex($val); + return (isset($_POST[$val])) ? addslashes($_POST[$val]) : ""; } // From http://php.net/manual/en/function.copy.php @@ -147,6 +86,45 @@ function create_user($user) return false; } +function _migrate_0($user, $db) +{ + try { + $db->query("ALTER TABLE gpass ADD access_token VARCHAR(32)"); + $db->query("ALTER TABLE gpass ADD shadow_login VARCHAR(32)"); + $db->query("ALTER TABLE gpass ADD salt VARCHAR(32)"); + + $db->query("CREATE TABLE db_version(version INTEGER)"); + $db->query("INSERT INTO db_version (version) VALUES (1)"); + } + catch(Exception $e) + { + $db->close(); + echo "
Unable to load database for user $user ! : $e
"; + return -1; + } + + return 0; +} + +function migrate_database($user, $db) +{ + global $TARGET_DB_VERSION; + + $migration_functions = ['_migrate_0']; + + $version = $db->querySingle("SELECT version FROM db_version"); + if ($version == false) + $version = 0; + + for($i=$version; $i<$TARGET_DB_VERSION; $i++) + { + if ($migration_functions[$i]($user, $db)) + return -1; + } + + return 0; +} + function load_database($user) { global $USERS_PATH; @@ -160,13 +138,17 @@ function load_database($user) return null; } + if (migrate_database($user, $db)) + return null; + // New access need to reset crypto unset($_SESSION['td']); return $db; } -function add_entry($user, $login, $password) +function add_entry($user, $login, $password, + $shadow_login, $salt, $access_token) { $db = load_database($user); @@ -184,8 +166,11 @@ function add_entry($user, $login, $password) return false; } - $result = $db->query("INSERT INTO gpass ('login', 'password') VALUES ('" . $login . "', '" . $password . "')"); + $result = $db->query("INSERT INTO gpass ('login', 'password', 'shadow_login', 'salt', 'access_token') VALUES + ('" . $login . "', '" . $password . "', '" . $shadow_login . "', '" . $salt . "', '" . $access_token . "')"); + error_log("INSERT INTO gpass ('login', 'password', 'shadow_login', 'salt', 'access_token') VALUES + ('" . $login . "', '" . $password . "', '" . $shadow_login . "', '" . $salt . "', '" . $access_token . "')"); $db->close(); echo "OK"; @@ -193,7 +178,7 @@ function add_entry($user, $login, $password) return true; } -function delete_entry($user, $login) +function delete_entry($user, $login, $access_token) { $db = load_database($user); @@ -203,19 +188,26 @@ function delete_entry($user, $login) return false; } - $db->query("DELETE FROM gpass WHERE login='" . $login . "'"); - - $db->close(); - - echo "OK"; - - return true; + $db_ac = $db->querySingle("SELECT access_token FROM gpass WHERE login='" . $login . "'"); + if (strlen($db_ac) != 0 && strcmp($db_ac, $access_token)) + { + $db->close(); + echo "Bad access token"; + return false; + } + else + { + $db->query("DELETE FROM gpass WHERE login='" . $login . "'"); + $db->close(); + echo "OK"; + return true; + } } -function update_entry($user, $mkey, $old_login, $url, $login, $password) +function update_entry($user, $mkey, $old_login, $url, $login, $password, $shadow_login, $salt, $old_access_token, $new_access_token) { - if (delete_entry($user, $old_login)) - return add_entry($user, $mkey, $url, $login, $password); + if (delete_entry($user, $old_login, $old_access_token)) + return add_entry($user, $mkey, $url, $login, $password, $shadow_login, $salt, $new_access_token); return false; } @@ -228,12 +220,60 @@ function list_entries($user) $result = $db->query("SELECT * FROM gpass"); - echo "entries\n"; + $first = false; + header('Content-Type: application/json'); + echo "{ \"entries\" : [\n"; while (($row = $result->fetchArray())) { - echo $row['login'] . ";" . $row['password'] . "\n"; + if ($first) echo ","; + else $first = true; + if (!strlen($row['shadow_login'])) + echo "{\"login\" : \"" . $row['login'] . "\", \"password\" : \"" . $row['password'] . "\" }\n"; + else + echo "{\"shadow_login\" : \"" . $row['shadow_login'] . "\", \"salt\" : \"" . $row['salt'] . "\" }\n"; } + + echo "]}"; + + $db->close(); +} + +function get_secure_entries($user, $access_tokens) +{ + $db = load_database($user); + + if ($db == null) return; + + $query = "SELECT access_token, login, password FROM gpass WHERE access_token IN ("; + $first = false; + + foreach (preg_split("/,/", $access_tokens) as $ac) + { + /* error_log($ac); */ + if ($first) $query .= ", "; + else $first = true; + $query .= "'$ac'"; + } + $query .= ")"; + + error_log($query); + $result = $db->query($query); + + header('Content-Type: application/json'); + $first = false; + echo "{ \"entries\" : [\n"; + + while (($row = $result->fetchArray())) + { + if ($first) echo ","; + else $first = true; + echo "{\"access_token\" : \"" . $row['access_token'] . "\", \"login\" : \"" . $row['login'] . "\", \"password\" : \"" . $row['password'] . "\" }\n"; + } + + echo "]}"; + + $db->close(); } ?> \ No newline at end of file diff --git a/server/index.php b/server/index.php index 3887aee..31155ca 100644 --- a/server/index.php +++ b/server/index.php @@ -24,25 +24,49 @@ include('conf.php'); session_start(); -$user = ""; +$user = ''; if ($ADMIN_MODE && isset($_POST['create_user'])) { - if (create_user($_POST['user'])) + $user = addslashes($_POST['user']); + if (create_user($user)) $user = $_POST['user']; + else + $user = ''; } else { + $user = sanitize('user'); + $login = sanitize('login'); + $shadow_login = sanitize('shadow_login'); + $password = sanitize('password'); + $access_token = sanitize('access_token'); + $access_tokens = sanitize('access_tokens'); + $salt = sanitize('salt'); + + if (isset($_POST['get_secure_passwords']) && isset($_POST['user']) && + isset($_POST['access_tokens'])) + return get_secure_entries($user, $access_tokens); + if (isset($_POST['get_passwords']) && isset($_POST['user'])) - return list_entries($_POST['user']); + return list_entries($user); if (isset($_POST['add_entry']) && isset($_POST['user']) && - isset($_POST['login']) && isset($_POST['password'])) - return add_entry($_POST['user'], $_POST['login'], $_POST['password']); + isset($_POST['login']) && isset($_POST['password']) && + isset($_POST['shadow_login']) && isset($_POST['salt']) && + isset($_POST['access_token']) ) + return add_entry($user, + $login, + $password, + $shadow_login, + $salt, + $access_token); if (isset($_POST['delete_entry']) && isset($_POST['user']) && - isset($_POST['login'])) - return delete_entry($_POST['user'], $_POST['login']); + isset($_POST['login']) && isset($_POST['access_token'])) + return delete_entry($user, + $login, + $access_token); } ?> @@ -50,24 +74,24 @@ else - - + + - - - - - - + + + + + + gPass : global Password
> diff --git a/server/init.sql b/server/init.sql index 8f0eb9e..1163973 100755 --- a/server/init.sql +++ b/server/init.sql @@ -1 +1,3 @@ -CREATE TABLE gpass(login VARCHAR(512) PRIMARY KEY, password VARCHAR(512)); \ No newline at end of file +CREATE TABLE gpass(login VARCHAR(512) PRIMARY KEY, password VARCHAR(512), shadow_login VARCHAR(32), salt VARCHAR(32), access_token VARCHAR(32)); +CREATE TABLE db_version(version INTEGER); +INSERT INTO db_version VALUES (1); \ No newline at end of file diff --git a/server/ref/gpass.bdd b/server/ref/gpass.bdd old mode 100755 new mode 100644 index 79a018e56367361df1ecf2300b8c96851a280227..c2cdad1c8ab04f2cfe4c1d8300bdeaf905e9ef8a GIT binary patch delta 238 zcmZpWXi%6SEy&Ekz`z2;Fu*iX$5@z|LHCg=FY_M;W~O@#%o@xWneJ^Y%wY=gU}h6n zRAh`TNleN~O-YI`OD!tS%+EuxSe%1g9Yb6dLL8lZTon-F8c>xAo_-;&?yfbMn(O^Ay4ygPc7agEWkdG=VaSIVGqfiOI>S#l`U@ j`Pr#RYB!%_Hsavo1$u-L