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