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 79a018e..c2cdad1 Binary files a/server/ref/gpass.bdd and b/server/ref/gpass.bdd differ diff --git a/server/resources/gpass.css b/server/resources/gpass.css index 1c68df1..a48dc6f 100755 --- a/server/resources/gpass.css +++ b/server/resources/gpass.css @@ -99,7 +99,7 @@ body { } #scorebar { - background-image: url(/ressources/bg_strength_gradient.jpg); + background-image: url(/resources/bg_strength_gradient.jpg); background-repeat: no-repeat; background-position: 0 0; position:absolute; diff --git a/server/resources/gpass.js b/server/resources/gpass.js index 827d452..0334ec9 100755 --- a/server/resources/gpass.js +++ b/server/resources/gpass.js @@ -50,20 +50,21 @@ Element.prototype.removeAllChilds = function() { this.removeChild(this.childNodes[0]); }; - -function generate_password() +function generate_random(size, only_ascii) { // symbols 32 - 47 / 58 - 64 / 91 - 96 / 123 - 126 // numbers 48 - 57 // upper 65 - 90 // lower 97 - 122 // Give priority to letters (65 - 122 duplicated in front and end of array) - var symbols = new Array(65, 90, 97, 122, 40, 47, 48, 57, 65, 90, 97, 122, 123, 126, 65, 90, 97, 122); - - field = document.getElementById("new_password"); + var symbols; + if (only_ascii) + symbols = new Array(65, 90, 97, 122, 40, 47, 48, 57, 65, 90, 97, 122, 123, 126, 65, 90, 97, 122); + else + symbols = new Array(1, 255); var res = ""; - while (res.length < 16) + while (res.length < size) { a = Math.round(Math.random() * (symbols.length/2) * 2); diff = symbols[a+1] - symbols[a]; @@ -73,7 +74,12 @@ function generate_password() res += String.fromCharCode(r + symbols[a]); } - field.value = res; + return res; +} + +function generate_password() +{ + document.getElementById("new_password").value = generate_random(16, true); } function url_domain(data) { @@ -111,7 +117,7 @@ var passwords; var current_user = ""; var current_mkey = ""; -function PasswordEntry (ciphered_login, ciphered_password) { +function PasswordEntry (ciphered_login, ciphered_password, salt="", shadow_login="") { this.ciphered_login = ciphered_login; this.ciphered_password = ciphered_password; this.unciphered = false; @@ -119,6 +125,9 @@ function PasswordEntry (ciphered_login, ciphered_password) { this.clear_login = ""; this.clear_password = ""; this.masterkey = ""; + this.salt = salt; + this.shadow_login = shadow_login; + this.access_token = ""; this.decrypt = function(masterkey) { @@ -163,25 +172,43 @@ function PasswordEntry (ciphered_login, ciphered_password) { { return !(this.isUnciphered(masterkey)); } + + this.shadow_login_to_access_token = function(masterkey) + { + var aes = new AES(); + var key = pkdbf2(hex2a(masterkey), hex2a(this.salt), pkdbf2_level, 256/8); + var a_key = aes.init(hex2a(key)); + this.access_token = aes.encryptLongString(hex2a(this.shadow_login), a_key); + this.access_token = a2hex(this.access_token); + aes.finish(); + } + + this.generate_access_token = function(masterkey) + { + this.salt = a2hex(generate_random(16, false)); + this.shadow_login = a2hex(generate_random(16, false)); + + return this.shadow_login_to_access_token(masterkey); + } } function list_all_entries(user) { passwords = new Array(); - + req = new XMLHttpRequest(); req.addEventListener("load", function(evt) { - entries = this.responseText.split("\n"); - if (entries[0] == "entries") + j = JSON.parse(this.responseText); + for(i=0; i