Merge branch 'master' of soutade.fr:gpass

This commit is contained in:
Grégory Soutadé 2017-04-17 20:42:08 +02:00
commit 09e0d85d97
14 changed files with 701 additions and 716 deletions

View File

@ -44,7 +44,7 @@ function load_database()
// Brute force ??
$db->close();
return null;
}
}
$db->query("UPDATE conf SET last_access_time=$usec");
$db->close();
$db = new SQLite3("./gpass.bdd", SQLITE3_OPEN_READONLY);
@ -69,9 +69,7 @@ $statement = $db->prepare("SELECT password FROM gpass WHERE login=:login");
echo "protocol=gpass-$PROTOCOL_VERSION\n";
if ($PBKDF2_LEVEL != 1000)
{
echo "pbkdf2_level=$PBKDF2_LEVEL\n";
}
for ($i=0; $i<$MAX_PASSWORDS_PER_REQUEST && isset($_POST["k$i"]); $i++)
{
@ -81,6 +79,7 @@ for ($i=0; $i<$MAX_PASSWORDS_PER_REQUEST && isset($_POST["k$i"]); $i++)
$result->finalize();
if (isset($row["password"]))
{
echo "matched_key=" . $i . "\n";
echo "pass=" . $row["password"] . "\n";
break;
}

View File

@ -37,7 +37,7 @@ $ADMIN_MODE=true;
!! Warning !! This impact master keys. So if you change
this value with existings masterkeys, they will unusable !
*/
$BKDF2_LEVEL=1000;
$PBKDF2_LEVEL=1000;
/*
This is a security feature : It protects from database dump
@ -50,16 +50,13 @@ $BKDF2_LEVEL=1000;
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 !
This option is backward compatible with old version < 0.6
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;
$USE_SHADOW_LOGINS=1;
/*
Protection against DDoS.
@ -83,4 +80,11 @@ $REQUESTS_MIN_DELAY=1000;
Clear master keys and reset passwords after 15 minutes of inactivity
*/
$CLEAR_TIME=15*60*1000;
/*
The first crypto schema use an AES-ECB process to encrypt logins.
It's used until version 0.7.
Since version 0.8, we use AES-CBC + SHA256.
*/
$CRYPTO_V1_COMPATIBLE=1;
?>

View File

@ -20,11 +20,13 @@
/*
login is stored as :
@@url;login
url;login + 16 bytes padding * \0 + sha256(url;login + padding)[8:24]
Password is salted (3 random characters) and encrypted
All is encrypted with AES256 and key : PBKDF2(hmac_sha256, master key, url, 1000)
All is encrypted with AES256-CBC and key PBKDF2(hmac_sha256, master key, server url, 1000)
level is server configurable
iv is PBKDF2(hmac_sha256, server url, master key, 1000)[0:16]
*/
$MAX_ENTRY_LEN = 512;
$USERS_PATH = "./users/";
@ -129,10 +131,10 @@ function migrate_database($user, $db)
$migration_functions = ['_migrate_0', '_migrate_1'];
$version = $db->querySingle("SELECT db_version FROM conf");
if ($version == false || $version == -1)
if ($version == NULL || $version == -1)
{
$version = $db->querySingle("SELECT version FROM db_version");
if ($version == false || $version == -1)
if ($version == NULL || $version == -1)
$version = 0;
}
@ -170,6 +172,8 @@ function load_database($user)
function add_entry($user, $login, $password,
$shadow_login, $salt, $access_token)
{
global $USE_SHADOW_LOGINS;
$db = load_database($user);
if ($db == null)
@ -178,22 +182,30 @@ function add_entry($user, $login, $password,
return false;
}
if ($USE_SHADOW_LOGINS && (strlen($shadow_login) != 32 ||
strlen($salt) != 32 || strlen($access_token) != 32))
{
$db->close();
echo "Shadow login not configured";
return false;
}
$count = $db->querySingle("SELECT COUNT(*) FROM gpass WHERE login='" . $login . "'");
if ($count != 0)
if ($count != NULL && $count != 0)
{
echo "Entry already exists";
return false;
}
$result = $db->query("INSERT INTO gpass ('login', 'password', 'shadow_login', 'salt', 'access_token') VALUES
$result = $db->exec("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();
if ($result == FALSE)
if (!$result)
{
echo "Error " . $db->lastErrorMsg();
return false;
@ -207,6 +219,8 @@ function add_entry($user, $login, $password,
function delete_entry($user, $login, $access_token)
{
global $USE_SHADOW_LOGINS;
$db = load_database($user);
if ($db == null)
@ -215,28 +229,29 @@ function delete_entry($user, $login, $access_token)
return false;
}
$db_ac = $db->querySingle("SELECT access_token FROM gpass WHERE login='" . $login . "'");
if (strlen($db_ac) != 0 && strcmp($db_ac, $access_token))
if ($USE_SHADOW_LOGINS)
{
$db->close();
echo "Bad access token";
$db_ac = $db->querySingle("SELECT access_token FROM gpass WHERE login='" . $login . "'");
if ($db_ac != NULL && strcmp($db_ac, $access_token))
{
$db->close();
echo "Bad access token";
return false;
}
}
$result = $db->exec("DELETE FROM gpass WHERE login='" . $login . "'");
$db->close();
if (!$result)
{
echo "Error " . $db->lastErrorMsg();
return false;
}
else
{
$result = $db->query("DELETE FROM gpass WHERE login='" . $login . "'");
$db->close();
if ($result == FALSE)
{
echo "Error " . $db->lastErrorMsg();
return false;
}
else
{
echo "OK";
return true;
}
echo "OK";
return true;
}
}
@ -250,6 +265,8 @@ function update_entry($user, $mkey, $old_login, $url, $login, $password, $shadow
function list_entries($user)
{
global $USE_SHADOW_LOGINS;
$db = load_database($user);
if ($db == null) return;
@ -264,7 +281,7 @@ function list_entries($user)
{
if ($first) echo ",";
else $first = true;
if (!strlen($row['shadow_login']))
if (!strlen($row['shadow_login']) || !$USE_SHADOW_LOGINS)
echo "{\"login\" : \"" . $row['login'] . "\", \"password\" : \"" . $row['password'] . "\" }\n";
else
echo "{\"shadow_login\" : \"" . $row['shadow_login'] . "\", \"salt\" : \"" . $row['salt'] . "\" }\n";

View File

@ -18,9 +18,8 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
include('functions.php');
include('conf.php');
include('functions.php');
session_start();
@ -80,17 +79,31 @@ else
<?php
echo "pbkdf2_level=$PBKDF2_LEVEL; use_shadow_logins=$USE_SHADOW_LOGINS;\n";
echo "CLEAR_TIME=$CLEAR_TIME; // Clear master key after 15 minutes\n";
echo "CRYPTO_V1_COMPATIBLE=$CRYPTO_V1_COMPATIBLE;\n";
?>
document.addEventListener('DOMContentLoaded', function() {
window.onscroll = function(ev) {
document.getElementById("buttonTop").className = (window.pageYOffset > 500) ? "cVisible" : "cInvisible";
};
});
function scrollToTop()
{
if (window.pageYOffset == 0)
return;
target = (window.innerHeight) ? window.innerHeight/5 : 200;
toScroll = (window.pageYOffset > target) ? target : window.pageYOffset;
window.scrollBy(0, -toScroll);
setTimeout(scrollToTop, 24);
}
</script>
<script src="resources/jsaes.js"></script>
<script src="resources/jssha256.js"></script>
<script src="resources/hmac.js"></script>
<script src="resources/pbkdf2.js"></script>
<script src="resources/misc.js"></script>
<script src="resources/gpass.js"></script>
<script src="resources/pwdmeter.js"></script>
<title>gPass : global Password</title>
</head>
<body onload="start();">
<div><a id="buttonTop" class="cInvisible" onclick="scrollToTop();"></a></div>
<div id="logo">
<a href="http://indefero.soutade.fr/p/gpass"><img src="resources/gpass.png" alt="logo"/></a>
</div>
@ -137,8 +150,6 @@ else
echo "<div id=\"addon_address\">Current addon address is : https://" . $_SERVER['SERVER_NAME'] . "/" . $user . "</div>\n";
}
?>
<div id="passwords">
</div>
<div id="add_new_password">
<?php
global $user;
@ -158,6 +169,8 @@ if ($user != "")
}
?>
</div>
<div id="passwords">
</div>
<div id="update_masterkey">
<?php
global $user;

View File

@ -126,4 +126,37 @@ body {
position:absolute;
width: 100px;
z-index: 0;
}
}
/* From http://www.trucsweb.com/tutoriels/javascript/retour-haut/ */
a#buttonTop{
border-radius:3px;
padding:0 10px 10px 10px;
font-size:3em;
text-align:center;
color:#fff;
background:rgba(0, 0, 0, 0.25);
position:fixed;
right:3%;
opacity:1;
z-index:99999;
transition:all ease-in 0.2s;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
text-decoration: none;
}
a#buttonTop:before{ content: "\25b2"; }
a#buttonTop:hover{
background:rgba(0, 0, 0, 1);
transition:all ease-in 0.2s;
}
a#buttonTop.cInvisible{
bottom:-35px;
opacity:0;
transition:all ease-in 0.5s;
}
a#buttonTop.cVisible{
bottom:20px;
opacity:1;
}

View File

@ -119,17 +119,19 @@ function a2hex(str) {
return hex;
}
function derive_mkey(user, mkey)
async function derive_mkey(user, mkey)
{
url = url_domain(document.URL) + "/" + user;
mkey = a2hex(pbkdf2(mkey, url, pbkdf2_level, 256/8));
return mkey;
url = url_domain(server_url) + "/" + user;
global_iv = simple_pbkdf2(url, mkey, pbkdf2_level);
return crypto_pbkdf2(mkey, url, pbkdf2_level);
}
var passwords;
var passwords = null;
var current_user = "";
var current_mkey = "";
var clearTimer = null;
var global_iv = null;
var server_url = document.documentURI;
function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
this.ciphered_login = ciphered_login;
@ -138,7 +140,7 @@ function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
this.clear_url = "";
this.clear_login = "";
this.clear_password = "";
this.masterkey = "";
this.masterkey = null;
this.salt = salt;
this.shadow_login = shadow_login;
this.access_token = "";
@ -149,73 +151,105 @@ function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
this.clear_url = "";
this.clear_login = "";
this.clear_password = "";
this.masterkey = "";
this.masterkey = null;
this.salt = salt;
}
this.encrypt = function(masterkey)
this.reset_master_key = function()
{
this.masterkey = null;
}
this.encrypt = async function(masterkey)
{
if (masterkey == this.masterkey)
return true;
if (masterkey == "" || this.clear_url == "" || this.clear_login == "")
if (masterkey == null || this.clear_url == "" || this.clear_login == "")
return false;
ciphered_login = "@@" + this.clear_url + ";" + this.clear_login;
var ciphered_login = this.clear_url + ";" + this.clear_login;
while ((ciphered_login.length % 16))
ciphered_login += "\0";
var computed_hash = await digest(ciphered_login);
ciphered_login += computed_hash.slice(8, 24);
var iv = await global_iv;
iv = iv.slice(0, 16);
// Add salt
ciphered_password = this.clear_password + generate_random(3, false);
var ciphered_password = generate_random(3, false) + this.clear_password ;
aes = new AES();
a_masterkey = aes.init(hex2a(masterkey));
this.ciphered_login = a2hex(aes.encryptLongString(ciphered_login, a_masterkey));
this.ciphered_password = a2hex(aes.encryptLongString(ciphered_password, a_masterkey));
aes.finish();
this.ciphered_login = a2hex(await encrypt_cbc(masterkey, iv, ciphered_login));
this.ciphered_password = a2hex(await encrypt_cbc(masterkey, iv, ciphered_password));
this.unciphered = true;
this.masterkey = masterkey;
if (use_shadow_logins)
this.generate_access_token(masterkey);
await this.generate_access_token(masterkey);
}
this.decrypt = function(masterkey)
this.decrypt = async function(masterkey)
{
if (masterkey == this.masterkey && this.unciphered == true)
return true;
if (masterkey == "" || this.unciphered == true)
if (masterkey == null)
return false;
aes = new AES();
a_masterkey = aes.init(hex2a(masterkey));
login = aes.decryptLongString(hex2a(this.ciphered_login), a_masterkey);
login = login.replace(/\0*$/, "");
if (login.indexOf("@@") != 0)
if (masterkey == this.masterkey)
return (this.unciphered == true);
var old = false;
var iv = await global_iv;
iv = iv.slice(0, 16);
var login = await decrypt_cbc(masterkey, iv, hex2a(this.ciphered_login));
var computed_digest = await digest(login.slice(0, login.length-16))
computed_digest = computed_digest.slice(8, 24);
if (login.indexOf(computed_digest) == login.length-16)
{
aes.finish();
return false;
login = login.slice(0, login.length-16).replace(/\0*$/, "");
}
// Remove @@
login = login.substring(2);
else if (CRYPTO_V1_COMPATIBLE)
{
login = await decrypt_ecb(masterkey, hex2a(this.ciphered_login));
if (login.indexOf("@@") != 0)
{
return false;
}
login = login.replace(/\0*$/, "");
// Remove @@
login = login.substring(2);
old = true;
}
else
return false;
infos = login.split(";");
this.clear_url = infos[0];
this.clear_login = infos[1];
this.clear_password = aes.decryptLongString(hex2a(this.ciphered_password), a_masterkey);
if (old)
{
this.clear_password = await decrypt_ecb(masterkey, hex2a(this.ciphered_password));
// Remove salt
this.clear_password = this.clear_password.replace(/\0*$/, "");
this.clear_password = this.clear_password.substr(0, this.clear_password.length-3);
}
else
{
this.clear_password = await decrypt_cbc(masterkey, iv, hex2a(this.ciphered_password));
// Remove salt
this.clear_password = this.clear_password.replace(/\0*$/, "");
this.clear_password = this.clear_password.substr(3, this.clear_password.length);
}
this.unciphered = true;
this.masterkey = masterkey;
aes.finish();
// Remove salt
this.clear_password = this.clear_password.replace(/\0*$/, "");
this.clear_password = this.clear_password.substr(0, this.clear_password.length-3);
return true;
}
this.isUnciphered = function(masterkey)
{
return (this.unciphered == true && masterkey == this.masterkey && masterkey != "")
return (this.unciphered == true && masterkey == this.masterkey && masterkey != null)
}
this.isCiphered = function(masterkey)
@ -223,28 +257,24 @@ function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
return !(this.isUnciphered(masterkey));
}
this.shadow_login_to_access_token = function(masterkey)
this.shadow_login_to_access_token = async function(masterkey)
{
var aes = new AES();
var key = pbkdf2(hex2a(masterkey), hex2a(this.salt), pbkdf2_level, 256/8);
var a_key = aes.init(hex2a(key));
this.access_token = aes.encryptLongString(hex2a(this.shadow_login), a_key);
this.access_token = await encrypt_ecb(masterkey, hex2a(this.shadow_login));
this.access_token = a2hex(this.access_token);
aes.finish();
}
this.generate_access_token = function(masterkey)
this.generate_access_token = async 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);
return await this.shadow_login_to_access_token(masterkey);
}
}
function clearMasterKey()
{
current_mkey = "";
current_mkey = null;
for(i=0; i<passwords.length; i++)
{
@ -269,6 +299,7 @@ function startClearTimer()
{
clearMasterKey();
change_master_key(false);
scrollToTop();
}
, CLEAR_TIME);
}
@ -290,7 +321,7 @@ function list_all_entries(user)
}
}
, false);
req.open("POST", document.documentURI, false);
req.open("POST", server_url, false);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
req.send("get_passwords=1&user=" + user);
}
@ -328,17 +359,17 @@ function update_stats()
}
// Remove all password without credentials
function put_ciphered_credentials(passwords, masterkey)
async function put_ciphered_credentials(passwords, masterkey)
{
for(var i=0; i<passwords.length; i++)
{
passwords[i].generate_access_token(masterkey);
await passwords[i].generate_access_token(masterkey);
remove_password_server(current_user, passwords[i].ciphered_login, '');
add_password_server(current_user, passwords[i]);
}
}
function get_ciphered_credentials(masterkey)
async function get_ciphered_credentials(masterkey)
{
access_tokens = '';
old_passwords = new Array();
@ -349,17 +380,21 @@ function get_ciphered_credentials(masterkey)
if (passwords[i].ciphered_login.length)
{
if (!passwords[i].access_token.length)
old_passwords.push(passwords[i]);
{
res = await passwords[i].decrypt(masterkey);
if(res)
old_passwords.push(passwords[i]);
}
continue;
}
passwords[i].shadow_login_to_access_token(masterkey);
await passwords[i].shadow_login_to_access_token(masterkey);
if (access_tokens.length) access_tokens += ",";
access_tokens += passwords[i].access_token;
}
if (old_passwords.length)
put_ciphered_credentials(old_passwords, masterkey);
await put_ciphered_credentials(old_passwords, masterkey);
if (!access_tokens.length)
return;
@ -380,21 +415,21 @@ function get_ciphered_credentials(masterkey)
}
}
}, false);
req.open("POST", document.documentURI, false);
req.open("POST", server_url, false);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
req.send("get_secure_passwords=1&user=" + user + "&access_tokens=" + access_tokens);
req.send("get_secure_passwords=1&user=" + current_user + "&access_tokens=" + access_tokens);
}
function change_master_key(warning_unciphered)
async function change_master_key(warning_unciphered)
{
var nb_unciphered = 0;
if (current_mkey.length && use_shadow_logins)
get_ciphered_credentials(current_mkey);
if (current_mkey && use_shadow_logins)
await get_ciphered_credentials(current_mkey);
for(i=0; i<passwords.length; i++)
{
if (passwords[i].decrypt(current_mkey))
if (await passwords[i].decrypt(current_mkey))
nb_unciphered++;
}
@ -534,7 +569,7 @@ function update_master_key(warning_unciphered)
addon_address = document.getElementById("addon_address");
addon_address.removeAllChilds();
addon_address.appendChild(document.createTextNode("Current addon address is : " + document.documentURI + current_user));
addon_address.appendChild(document.createTextNode("Current addon address is : " + server_url + current_user));
warning_unciphered = false;
}
@ -543,11 +578,16 @@ function update_master_key(warning_unciphered)
if (current_mkey != "")
{
for(i=0; i<passwords.length; i++)
{
passwords[i].reset_master_key();
}
current_mkey = derive_mkey(current_user, current_mkey);
startClearTimer();
}
else
{
current_mkey = null;
// Disable warning on empty master key (clear passwords from others)
warning_unciphered = false;
stopClearTimer();
@ -577,14 +617,14 @@ function add_password_server(user, pentry)
else
alert(resp);
}, false);
req.open("POST", document.documentURI, false);
req.open("POST", server_url, false);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
req.send("add_entry=1&user=" + user + "&login=" + pentry.ciphered_login + "&password=" + pentry.ciphered_password + "&shadow_login=" + pentry.shadow_login + "&salt=" + pentry.salt + "&access_token=" + pentry.access_token);
return ok;
}
function construct_pentry(user, url, password, login, mkey, derive_masterkey)
async function construct_pentry(user, url, password, login, mkey, derive_masterkey)
{
var ret = null;
@ -613,7 +653,7 @@ function construct_pentry(user, url, password, login, mkey, derive_masterkey)
}
if (derive_masterkey)
mkey = derive_mkey(current_user, mkey);
mkey = derive_mkey(user, mkey);
for(i=0; i<passwords.length; i++)
{
@ -632,7 +672,7 @@ function construct_pentry(user, url, password, login, mkey, derive_masterkey)
pentry.clear_url = url;
pentry.clear_login = login;
pentry.clear_password = password;
pentry.encrypt(mkey);
await pentry.encrypt(mkey);
return pentry;
}
@ -649,7 +689,7 @@ function remove_password_server(user, login, access_token)
else
alert(resp);
}, false);
req.open("POST", document.documentURI, false);
req.open("POST", server_url, false);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
req.send("delete_entry=1&user=" + user + "&login=" + login + "&access_token=" + access_token);
@ -679,26 +719,33 @@ function add_password()
mkey = inputs[i].value;
}
pentry = construct_pentry(current_user, url, password, login, mkey, true)
construct_pentry(current_user, url, password, login, mkey, true).then(
function (pentry) {
if (pentry == null) return false;
if (pentry == null) return;
res = add_password_server(current_user, pentry);
res = add_password_server(current_user, pentry);
if (!res) return false;
if (!res) return false;
for(i=0; i<passwords.length; i++)
{
passwords[i].reset_master_key();
}
current_mkey = pentry.masterkey;
passwords.push(pentry);
passwords.push(pentry);
change_master_key(false);
current_mkey = pentry.masterkey;
change_master_key(false);
for(i=0; i<inputs.length; i++)
{
if (inputs[i].getAttribute("type") == "text" ||
inputs[i].getAttribute("type") == "password")
inputs[i].value = "";
}
for(i=0; i<inputs.length; i++)
{
if (inputs[i].getAttribute("type") == "text" ||
inputs[i].getAttribute("type") == "password")
inputs[i].value = "";
}
startClearTimer();
});
return true;
}
@ -735,7 +782,8 @@ function delete_entry(entry_number)
var found = -1;
for(i=0; i<passwords.length; i++)
{
if (passwords[i].ciphered_login == ciphered_login.getAttribute("login"))
if (passwords[i].ciphered_login == ciphered_login.getAttribute("login") ||
passwords[i].shadow_login == ciphered_login.getAttribute("login"))
{
found = i;
break;
@ -814,23 +862,24 @@ function update_entry(entry_number)
if(!confirm("Are you sure want to update this entry ?"))
return;
pentry = construct_pentry(current_user, url, password, login, current_mkey, false);
construct_pentry(current_user, url, password, login, current_mkey, false).then(
function (pentry) {
if (pentry == null) return;
if (pentry == null) return;
ok = remove_password_server(current_user, passwords[found].ciphered_login, passwords[found].access_token);
if (!ok) return;
ok = remove_password_server(current_user, passwords[found].ciphered_login, passwords[found].access_token);
if (!ok) return;
ok = add_password_server(current_user, pentry);
if (!ok) return;
ok = add_password_server(current_user, pentry);
if (!ok) return;
passwords[found] = pentry;
ciphered_login.setAttribute("login", pentry.ciphered_login);
passwords[found] = pentry;
ciphered_login.setAttribute("login", pentry.ciphered_login);
alert("Entry updated");
alert("Entry updated");
});
}
function update_masterkey()
async function update_masterkey()
{
var url = "";
var login = "";
@ -851,12 +900,15 @@ function update_masterkey()
return;
oldmkey = derive_mkey(current_user, oldmkey);
old_global_iv = global_iv;
current_mkey = derive_mkey(current_user, newmkey);
new_global_iv = global_iv;
var found = 0;
for(i=0; i<passwords.length; i++)
{
if (passwords[i].decrypt(oldmkey))
global_iv = old_global_iv;
if (await passwords[i].decrypt(oldmkey))
{
ok = remove_password_server(current_user, passwords[i].ciphered_login, passwords[i].access_token);
if (!ok)
@ -865,7 +917,11 @@ function update_masterkey()
break;
}
passwords[i].encrypt(current_mkey);
if (use_shadow_logins)
await passwords[i].generate_access_token(current_mkey);
global_iv = new_global_iv;
await passwords[i].encrypt(current_mkey);
ok = add_password_server(current_user, passwords[i]);
if (!ok)
@ -904,7 +960,7 @@ function export_database()
if (text_link != null) window.URL.revokeObjectURL(text_link);
text = "<passwords user=\"" + current_user + "\" addon_address=\"" + document.documentURI + current_user + "\">\n";
text = "<passwords user=\"" + current_user + "\" addon_address=\"" + server_url + current_user + "\">\n";
for(i=0; i<passwords.length; i++)
{
if (!passwords[i].unciphered) continue;

View File

@ -1,291 +0,0 @@
/*
* jsaes version 0.1 - Copyright 2006 B. Poettering
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
/*
* http://point-at-infinity.org/jsaes/
*
* This is a javascript implementation of the AES block cipher. Key lengths
* of 128, 192 and 256 bits are supported.
*
* The well-functioning of the encryption/decryption routines has been
* verified for different key lengths with the test vectors given in
* FIPS-197, Appendix C.
*
* The following code example enciphers the plaintext block '00 11 22 .. EE FF'
* with the 256 bit key '00 01 02 .. 1E 1F'.
*
* AES_Init();
*
* var block = new Array(16);
* for(var i = 0; i < 16; i++)
* block[i] = 0x11 * i;
*
* var key = new Array(32);
* for(var i = 0; i < 32; i++)
* key[i] = i;
*
* AES_ExpandKey(key);
* AES_Encrypt(block, key);
*
* AES_Done();
*
* Report bugs to: jsaes AT point-at-infinity.org
*
*/
/******************************************************************************/
/*
AES_Init: initialize the tables needed at runtime. Call this function
before the (first) key expansion.
*/
function AES_Init() {
AES_Sbox_Inv = new Array(256);
for(var i = 0; i < 256; i++)
AES_Sbox_Inv[AES_Sbox[i]] = i;
AES_ShiftRowTab_Inv = new Array(16);
for(var i = 0; i < 16; i++)
AES_ShiftRowTab_Inv[AES_ShiftRowTab[i]] = i;
AES_xtime = new Array(256);
for(var i = 0; i < 128; i++) {
AES_xtime[i] = i << 1;
AES_xtime[128 + i] = (i << 1) ^ 0x1b;
}
}
/*
AES_Done: release memory reserved by AES_Init. Call this function after
the last encryption/decryption operation.
*/
function AES_Done() {
delete AES_Sbox_Inv;
delete AES_ShiftRowTab_Inv;
delete AES_xtime;
}
/*
AES_ExpandKey: expand a cipher key. Depending on the desired encryption
strength of 128, 192 or 256 bits 'key' has to be a byte array of length
16, 24 or 32, respectively. The key expansion is done "in place", meaning
that the array 'key' is modified.
*/
function AES_ExpandKey(key) {
var kl = key.length, ks, Rcon = 1;
switch (kl) {
case 16: ks = 16 * (10 + 1); break;
case 24: ks = 16 * (12 + 1); break;
case 32: ks = 16 * (14 + 1); break;
default:
alert("AES_ExpandKey: Only key lengths of 16, 24 or 32 bytes allowed!");
}
for(var i = kl; i < ks; i += 4) {
var temp = key.slice(i - 4, i);
if (i % kl == 0) {
temp = new Array(AES_Sbox[temp[1]] ^ Rcon, AES_Sbox[temp[2]],
AES_Sbox[temp[3]], AES_Sbox[temp[0]]);
if ((Rcon <<= 1) >= 256)
Rcon ^= 0x11b;
}
else if ((kl > 24) && (i % kl == 16))
temp = new Array(AES_Sbox[temp[0]], AES_Sbox[temp[1]],
AES_Sbox[temp[2]], AES_Sbox[temp[3]]);
for(var j = 0; j < 4; j++)
key[i + j] = key[i + j - kl] ^ temp[j];
}
}
/*
AES_Encrypt: encrypt the 16 byte array 'block' with the previously
expanded key 'key'.
*/
function AES_Encrypt(block, key) {
var l = key.length;
AES_AddRoundKey(block, key.slice(0, 16));
for(var i = 16; i < l - 16; i += 16) {
AES_SubBytes(block, AES_Sbox);
AES_ShiftRows(block, AES_ShiftRowTab);
AES_MixColumns(block);
AES_AddRoundKey(block, key.slice(i, i + 16));
}
AES_SubBytes(block, AES_Sbox);
AES_ShiftRows(block, AES_ShiftRowTab);
AES_AddRoundKey(block, key.slice(i, l));
}
/*
AES_Decrypt: decrypt the 16 byte array 'block' with the previously
expanded key 'key'.
*/
function AES_Decrypt(block, key) {
var l = key.length;
AES_AddRoundKey(block, key.slice(l - 16, l));
AES_ShiftRows(block, AES_ShiftRowTab_Inv);
AES_SubBytes(block, AES_Sbox_Inv);
for(var i = l - 32; i >= 16; i -= 16) {
AES_AddRoundKey(block, key.slice(i, i + 16));
AES_MixColumns_Inv(block);
AES_ShiftRows(block, AES_ShiftRowTab_Inv);
AES_SubBytes(block, AES_Sbox_Inv);
}
AES_AddRoundKey(block, key.slice(0, 16));
}
/******************************************************************************/
/* The following lookup tables and functions are for internal use only! */
AES_Sbox = new Array(99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,
118,202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,183,253,
147,38,54,63,247,204,52,165,229,241,113,216,49,21,4,199,35,195,24,150,5,154,
7,18,128,226,235,39,178,117,9,131,44,26,27,110,90,160,82,59,214,179,41,227,
47,132,83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,208,239,170,
251,67,77,51,133,69,249,2,127,80,60,159,168,81,163,64,143,146,157,56,245,
188,182,218,33,16,255,243,210,205,12,19,236,95,151,68,23,196,167,126,61,
100,93,25,115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,224,
50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,231,200,55,109,141,213,
78,169,108,86,244,234,101,122,174,8,186,120,37,46,28,166,180,198,232,221,
116,31,75,189,139,138,112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,
158,225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,223,140,161,
137,13,191,230,66,104,65,153,45,15,176,84,187,22);
AES_ShiftRowTab = new Array(0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11);
function AES_SubBytes(state, sbox) {
for(var i = 0; i < 16; i++)
state[i] = sbox[state[i]];
}
function AES_AddRoundKey(state, rkey) {
for(var i = 0; i < 16; i++)
state[i] ^= rkey[i];
}
function AES_ShiftRows(state, shifttab) {
var h = new Array().concat(state);
for(var i = 0; i < 16; i++)
state[i] = h[shifttab[i]];
}
function AES_MixColumns(state) {
for(var i = 0; i < 16; i += 4) {
var s0 = state[i + 0], s1 = state[i + 1];
var s2 = state[i + 2], s3 = state[i + 3];
var h = s0 ^ s1 ^ s2 ^ s3;
state[i + 0] ^= h ^ AES_xtime[s0 ^ s1];
state[i + 1] ^= h ^ AES_xtime[s1 ^ s2];
state[i + 2] ^= h ^ AES_xtime[s2 ^ s3];
state[i + 3] ^= h ^ AES_xtime[s3 ^ s0];
}
}
function AES_MixColumns_Inv(state) {
for(var i = 0; i < 16; i += 4) {
var s0 = state[i + 0], s1 = state[i + 1];
var s2 = state[i + 2], s3 = state[i + 3];
var h = s0 ^ s1 ^ s2 ^ s3;
var xh = AES_xtime[h];
var h1 = AES_xtime[AES_xtime[xh ^ s0 ^ s2]] ^ h;
var h2 = AES_xtime[AES_xtime[xh ^ s1 ^ s3]] ^ h;
state[i + 0] ^= h1 ^ AES_xtime[s0 ^ s1];
state[i + 1] ^= h2 ^ AES_xtime[s1 ^ s2];
state[i + 2] ^= h1 ^ AES_xtime[s2 ^ s3];
state[i + 3] ^= h2 ^ AES_xtime[s3 ^ s0];
}
}
function bin2String (array) {
var result = "";
for (var i = 0; i < array.length; i++) {
result += String.fromCharCode(parseInt(array[i], 2));
}
return result;
}
function string2Bin (str) {
var result = [];
for (var i = 0; i < str.length; i++) {
result.push(str.charCodeAt(i));
}
while ((result.length % 16))
result.push(0);
return result;
}
function bin2String (array) {
return String.fromCharCode.apply(String, array);
}
// http://osama-oransa.blogspot.fr/2012/03/using-aes-encrypting-in-java-script.html
function AES(a) {
this.init = function (myKey){
AES_Init();
var key = string2Bin(myKey);
AES_ExpandKey(key);
return key;
}
this.encrypt = function ( inputStr,key ) {
var block = string2Bin(inputStr);
AES_Encrypt(block, key);
var data=bin2String(block);
return data;
}
this.decrypt = function ( inputStr,key ) {
block = string2Bin(inputStr);
AES_Decrypt(block, key);
var data=bin2String(block);
return data;
}
this.encryptLongString = function( myString,key ) {
if(myString.length>16){
var data='';
for(var i=0;i<myString.length;i=i+16){
data+=this.encrypt(myString.substr(i,16),key);
}
return data;
}else{
return this.encrypt(myString,key);
}
}
this.decryptLongString = function ( myString,key ) {
if(myString.length>16){
var data='';
for(var i=0;i<myString.length;i=i+16){
data+=this.decrypt(myString.substr(i,16),key);
}
return data;
}else{
return this.decrypt(myString,key);
}
}
this.finish = function(){
AES_Done();
}
}

View File

@ -1,261 +0,0 @@
/*
* A JavaScript implementation of the SHA256 hash function.
*
* FILE: sha256.js
* VERSION: 0.8
* AUTHOR: Christoph Bichlmeier <informatik@zombiearena.de>
*
* NOTE: This version is not tested thoroughly!
*
* Copyright (c) 2003, Christoph Bichlmeier
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* ======================================================================
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* SHA256 logical functions */
function rotateRight(n,x) {
return ((x >>> n) | (x << (32 - n)));
}
function choice(x,y,z) {
return ((x & y) ^ (~x & z));
}
function majority(x,y,z) {
return ((x & y) ^ (x & z) ^ (y & z));
}
function sha256_Sigma0(x) {
return (rotateRight(2, x) ^ rotateRight(13, x) ^ rotateRight(22, x));
}
function sha256_Sigma1(x) {
return (rotateRight(6, x) ^ rotateRight(11, x) ^ rotateRight(25, x));
}
function sha256_sigma0(x) {
return (rotateRight(7, x) ^ rotateRight(18, x) ^ (x >>> 3));
}
function sha256_sigma1(x) {
return (rotateRight(17, x) ^ rotateRight(19, x) ^ (x >>> 10));
}
function sha256_expand(W, j) {
return (W[j&0x0f] += sha256_sigma1(W[(j+14)&0x0f]) + W[(j+9)&0x0f] +
sha256_sigma0(W[(j+1)&0x0f]));
}
/* Hash constant words K: */
var K256 = new Array(
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
);
/* global arrays */
var ihash, count, buffer;
var sha256_hex_digits = "0123456789abcdef";
/* Add 32-bit integers with 16-bit operations (bug in some JS-interpreters:
overflow) */
function safe_add(x, y)
{
var lsw = (x & 0xffff) + (y & 0xffff);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xffff);
}
/* Initialise the SHA256 computation */
function sha256_init() {
ihash = new Array(8);
count = new Array(2);
buffer = new Array(64);
count[0] = count[1] = 0;
ihash[0] = 0x6a09e667;
ihash[1] = 0xbb67ae85;
ihash[2] = 0x3c6ef372;
ihash[3] = 0xa54ff53a;
ihash[4] = 0x510e527f;
ihash[5] = 0x9b05688c;
ihash[6] = 0x1f83d9ab;
ihash[7] = 0x5be0cd19;
}
/* Transform a 512-bit message block */
function sha256_transform() {
var a, b, c, d, e, f, g, h, T1, T2;
var W = new Array(16);
/* Initialize registers with the previous intermediate value */
a = ihash[0];
b = ihash[1];
c = ihash[2];
d = ihash[3];
e = ihash[4];
f = ihash[5];
g = ihash[6];
h = ihash[7];
/* make 32-bit words */
for(var i=0; i<16; i++)
W[i] = ((buffer[(i<<2)+3]) | (buffer[(i<<2)+2] << 8) | (buffer[(i<<2)+1]
<< 16) | (buffer[i<<2] << 24));
for(var j=0; j<64; j++) {
T1 = h + sha256_Sigma1(e) + choice(e, f, g) + K256[j];
if(j < 16) T1 += W[j];
else T1 += sha256_expand(W, j);
T2 = sha256_Sigma0(a) + majority(a, b, c);
h = g;
g = f;
f = e;
e = safe_add(d, T1);
d = c;
c = b;
b = a;
a = safe_add(T1, T2);
}
/* Compute the current intermediate hash value */
ihash[0] += a;
ihash[1] += b;
ihash[2] += c;
ihash[3] += d;
ihash[4] += e;
ihash[5] += f;
ihash[6] += g;
ihash[7] += h;
}
/* Read the next chunk of data and update the SHA256 computation */
function sha256_update(data, inputLen) {
var i, index, curpos = 0;
/* Compute number of bytes mod 64 */
index = ((count[0] >> 3) & 0x3f);
var remainder = (inputLen & 0x3f);
/* Update number of bits */
if ((count[0] += (inputLen << 3)) < (inputLen << 3)) count[1]++;
count[1] += (inputLen >> 29);
/* Transform as many times as possible */
for(i=0; i+63<inputLen; i+=64) {
for(var j=index; j<64; j++)
buffer[j] = data.charCodeAt(curpos++);
sha256_transform();
index = 0;
}
/* Buffer remaining input */
for(var j=0; j<remainder; j++)
buffer[j] = data.charCodeAt(curpos++);
}
/* Finish the computation by operations such as padding */
function sha256_final() {
var index = ((count[0] >> 3) & 0x3f);
buffer[index++] = 0x80;
if(index <= 56) {
for(var i=index; i<56; i++)
buffer[i] = 0;
} else {
for(var i=index; i<64; i++)
buffer[i] = 0;
sha256_transform();
for(var i=0; i<56; i++)
buffer[i] = 0;
}
buffer[56] = (count[1] >>> 24) & 0xff;
buffer[57] = (count[1] >>> 16) & 0xff;
buffer[58] = (count[1] >>> 8) & 0xff;
buffer[59] = count[1] & 0xff;
buffer[60] = (count[0] >>> 24) & 0xff;
buffer[61] = (count[0] >>> 16) & 0xff;
buffer[62] = (count[0] >>> 8) & 0xff;
buffer[63] = count[0] & 0xff;
sha256_transform();
}
/* Split the internal hash values into an array of bytes */
function sha256_encode_bytes() {
var j=0;
var output = new Array(32);
for(var i=0; i<8; i++) {
output[j++] = ((ihash[i] >>> 24) & 0xff);
output[j++] = ((ihash[i] >>> 16) & 0xff);
output[j++] = ((ihash[i] >>> 8) & 0xff);
output[j++] = (ihash[i] & 0xff);
}
return output;
}
/* Get the internal hash as a hex string */
function sha256_encode_hex() {
var output = new String();
for(var i=0; i<8; i++) {
for(var j=28; j>=0; j-=4)
output += sha256_hex_digits.charAt((ihash[i] >>> j) & 0x0f);
}
return output;
}
/* Get the internal hash as string */
function sha256_encode() {
var output = new String();
for(var i=0; i<8; i++) {
for(var j=3; j>=0; j--)
output += String.fromCharCode((ihash[i] >>> j*8) & 0xff);
}
return output;
}
/* Main function: returns a hex string representing the SHA256 value of the
given data */
function digest256 (data) {
sha256_init();
sha256_update(data, data.length);
sha256_final();
return sha256_encode();
// return sha256_encode_hex();
}
/* test if the JS-interpreter is working properly */
function sha256_self_test()
{
return sha256_digest("message digest") ==
"f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650";
}

220
server/resources/misc.js Normal file
View File

@ -0,0 +1,220 @@
/*
Copyright (C) 2013-2017 Grégory Soutadé
This file is part of gPass.
gPass is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gPass is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
var default_preferences = {"pbkdf2_level": 1000,
"account_url": "https://gpass-demo.soutade.fr/demo"};
var browser = browser;
if (typeof chrome !== 'undefined')
browser = chrome;
var crypto = crypto || window.crypto;
// https://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
function str2ab2(str) {
var chars = []
for (var i=0, strLen=str.length; i < strLen; i++) {
chars.push(str.charCodeAt(i));
}
return new Uint8Array(chars);
}
function str2ab(str) {
var buf = new ArrayBuffer(str.length); // 2 bytes for each char
// var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
var bufView = new Uint8Array(buf);
for (var i=0, strLen=str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return bufView;
}
function crypto_pbkdf2(mkey, salt, level)
{
AESCBC = {
name: "AES-CBC",
length: 256,
}
var key = str2ab(mkey);
return crypto.subtle.importKey("raw", key, {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"])
.then(function(key){
//sha-256
return crypto.subtle.deriveKey({
name: "PBKDF2",
salt: str2ab(salt),
iterations: level,
hash: "SHA-256",
}, key, AESCBC, false, ["encrypt", "decrypt", "unwrapKey", "wrapKey"])
.then(function(key) {
return key;
})
.catch(function(err){
console.log("Error derive key " + err);
});
})
.catch(function(err) {
console.log("Error import key" + err);
});
}
function simple_pbkdf2(mkey, salt, level)
{
AESCBC = {
name: "AES-CBC",
length: 256,
}
var key = str2ab(mkey);
return crypto.subtle.importKey("raw", key, {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"])
.then(function(key){
//sha-256
return crypto.subtle.deriveKey({
name: "PBKDF2",
salt: str2ab(salt),
iterations: level,
hash: "SHA-256",
}, key, AESCBC, true, ["unwrapKey", "wrapKey"])
.then(function(key) {
return crypto.subtle.exportKey("raw", key)
.then(function (key) {
return ab2str(key);
});
})
.catch(function(err){
console.log("Error derive key " + err);
});
})
.catch(function(err) {
console.log("Error import key" + err);
});
}
function _encrypt(mkey, iv, data)
{
while ((data.length % 16))
data += "\0";
data = str2ab(data);
promise = mkey.then(function(mkey){
return crypto.subtle.encrypt({
name: "AES-CBC",
iv: iv
}, mkey, data)})
.then(function(encrypted) {
return ab2str(encrypted);
})
.catch(function(encryption) {
console.log("Encryption rejected " + encryption);
});
return promise;
}
async function _decrypt(mkey, iv, data)
{
while ((data.length % 16))
data += "\0";
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
pkcs7_padding = new Uint8Array([16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]);
pkcs7_padding = await _encrypt(mkey, nulliv, ab2str(pkcs7_padding));
data = str2ab(data + pkcs7_padding);
promise = mkey.then(function(mkey){
return crypto.subtle.decrypt({
name: "AES-CBC",
iv: iv
}, mkey, data)})
.then(function(decrypted) {
return ab2str(decrypted);
})
.catch(function(decryption) {
console.log("Decryption rejected " + decryption);
});
return promise;
}
async function encrypt_ecb(mkey, data)
{
var result = "";
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
while (data.length > 16)
{
res = await _encrypt(mkey, nulliv, data.slice(0, 16));
// Remove PKCS7 padding
result += res.slice(0, 16);
data = data.slice(16);
}
res = await _encrypt(mkey, nulliv, data);
result += res.slice(0, 16);
return result;
}
async function decrypt_ecb(mkey, data)
{
var result = "";
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
while (data.length > 16)
{
res = await _decrypt(mkey, nulliv, data.slice(0, 16));
// Remove PKCS7 padding
result += res.slice(0, 16);
data = data.slice(16);
}
res = await _decrypt(mkey, nulliv, data);
result += res.slice(0, 16);
return result;
}
async function encrypt_cbc(mkey, iv, data)
{
var result = await _encrypt(mkey, str2ab(iv), data);
// Remove PKCS7 padding
return result.slice(0, result.length-16);
}
async function decrypt_cbc(mkey, iv, data)
{
var result = await _decrypt(mkey, str2ab(iv), data);
// Remove PKCS7 padding
return result.slice(0, result.length-16);
}
async function digest(data)
{
return crypto.subtle.digest("SHA-256", str2ab(data)).then(function (hash) {
return ab2str(hash);
});
}

201
server/tests/test.js Normal file
View File

@ -0,0 +1,201 @@
server_url = "https://gpass.soutade.fr";
current_user = "test-v7";
pbkdf2_level = 1000;
CRYPTO_V1_COMPATIBLE = 1;
use_shadow_logins = false;
/*
Must contains :
URL login password masterkey
-----------------------------------------------------------------
v7 format
test test test test
test2 test2 test2 test2 (+shadow login)
test16 test16 testtesttesttest test16
test17 test17 testtesttesttestt test17
v8 format
testv8 testv8 testv8 testv8
testv8_2 testv8_2 testv8_2 testv8_2 (+shadow login)
testv8_16 testv8_16 testtesttesttest testv8_16
testv8_17 testv8_17 testtesttesttestt testv8_17
We assume shadow logins are enabled in server side
*/
function alert(a) {}
function nb_unciphered_passwords(passwords)
{
var nb_unciphered = 0;
for(i=0;i<passwords.length; i++)
{
if (passwords[i].unciphered)
nb_unciphered++;
}
return nb_unciphered;
}
function find_password(passwords, login)
{
for(i=0; i<passwords.length; i++)
{
if (passwords[i].clear_login == login)
return passwords[i];
}
return null;
}
async function decrypt_passwords(passwords, masterkey)
{
var nb_unciphered = 0;
for(nb_unciphered = 0, i=0;i<passwords.length; i++)
{
res = await passwords[i].decrypt(current_mkey);
if (res == true)
nb_unciphered++;
}
return nb_unciphered;
}
QUnit.test( "Testbench", async function( assert ) {
assert.ok( passwords == null, "Initial passwords null" );
list_all_entries(current_user);
assert.ok( passwords != null, "Retrieved passwords" );
assert.equal( passwords.length, 8, "8 passwords retrieved" );
var shadow_logins = 0;
for(i=0;i<passwords.length; i++)
if (passwords[i].shadow_login.length)
shadow_logins++;
assert.ok( shadow_logins == 2, "2 shadow logins" );
current_mkey = derive_mkey(current_user, "test");
var nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal( nb_unciphered, 1, "test #1 decrypted" );
await get_ciphered_credentials(current_mkey);
res = nb_unciphered_passwords(passwords);
assert.equal(res, 1, "No more passwords unciphered with shadow logins");
current_mkey = derive_mkey(current_user, "test2");
// Get ciphered credentials for "test2"
await get_ciphered_credentials(current_mkey);
res = nb_unciphered_passwords(passwords);
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Shadow logins OK");
current_mkey = derive_mkey(current_user, "test16");
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Test16 OK");
current_mkey = derive_mkey(current_user, "test17");
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Test17 OK");
// V8
current_mkey = derive_mkey(current_user, "testv8");
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Testv8 OK");
current_mkey = derive_mkey(current_user, "testv8_2");
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 0, "Testv8_2 without shadow login");
await get_ciphered_credentials(current_mkey);
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Testv8_2 OK");
current_mkey = derive_mkey(current_user, "testv8_16");
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Testv8_16 OK");
current_mkey = derive_mkey(current_user, "testv8_17");
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Testv8_17 OK");
nb_unciphered = nb_unciphered_passwords(passwords);
assert.equal(nb_unciphered, 8, "All passwords unciphered");
password = find_password(passwords, "testv8");
var ok = remove_password_server(current_user, passwords[i].ciphered_login,
passwords[i].access_token);
assert.equal(ok, true, "Remove OK");
alert(passwords[i].ciphered_login);
ok = remove_password_server(current_user, passwords[i].ciphered_login,
passwords[i].access_token);
assert.equal(ok, true, "Double remove OK");
password = find_password(passwords, "testv8_2");
ok = remove_password_server(current_user, passwords[i].ciphered_login,
"");
assert.equal(ok, false, "Remove without access token OK");
ok = remove_password_server(current_user, passwords[i].ciphered_login,
"AAAAAAAAAAAAAAAA");
assert.equal(ok, false, "Remove Bad access token");
res = await construct_pentry(current_user, "testv8_new", "testv8_new", "testv8_new", "testv8_new", true).then(
function (pentry) {
if (pentry == null) return false;
return add_password_server(current_user, pentry);
});
assert.equal(res, false, "Add without access token OK");
use_shadow_logins = true;
res = await construct_pentry(current_user, "testv8_new", "testv8_new", "testv8_new", "testv8_new", true).then(
function (pentry) {
if (pentry == null) return false;
return add_password_server(current_user, pentry);
});
assert.equal(res, true, "Add with access token OK");
res = add_password_server(current_user, passwords[passwords.length-1]);
assert.equal(res, false, "Double add OK");
res = await construct_pentry(current_user, "testv8_new2", "testv8_new2", "testv8_new2", "testv8_new2", true).then(
function (pentry) {
if (pentry == null) return false;
pentry.shadow_login = "AAA";
return add_password_server(current_user, pentry);
});
assert.equal(res, false, "Add with truncated shadow login OK");
password = find_password(passwords, "test16");
ok = remove_password_server(current_user, password.ciphered_login,
password.access_token);
assert.equal(ok, true, "Remove v7");
password = find_password(passwords, "test16");
ok = remove_password_server(current_user, password.ciphered_login,
"AAAAAAAAAAAAAAAA");
assert.equal(ok, true, "Remove v7 bad access token");
});
QUnit.test( "Updated database", async function( assert ) {
passwords = null;
list_all_entries(current_user);
assert.ok( passwords != null, "Passed!" );
assert.equal( passwords.length, 7, "7 passwords retrieved" );
current_mkey = derive_mkey(current_user, "testv8");
var nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal( nb_unciphered, 0, "Password removed" );
current_mkey = derive_mkey(current_user, "testv8_new");
await get_ciphered_credentials(current_mkey);
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Password added");
});

17
server/tests/tests.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>QUnit Example</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.3.0.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="https://code.jquery.com/qunit/qunit-2.3.0.js"></script>
<script src="../resources/misc.js"></script>
<script src="../resources/gpass.js"></script>
<script src="test.js"></script>
</body>
</html>

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +1,4 @@
<?php
/*
Copyright (C) 2013 Grégory Soutadé
@ -17,30 +18,6 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
function hmac256(key, message) {
var ipad = "";
var opad = "";
include "../../_user";
if (key.length > 512/8)
{
key = digest256(key);
}
for(i=0; i<512/8; i++)
{
if (i >= key.length)
{
ipad += String.fromCharCode(0x36);
opad += String.fromCharCode(0x5c);
}
else
{
ipad += String.fromCharCode(key.charCodeAt(i) ^ 0x36);
opad += String.fromCharCode(key.charCodeAt(i) ^ 0x5c);
}
}
result = digest256(opad + digest256(ipad + message));
return result;
}
?>