1016 lines
26 KiB
JavaScript
Executable File
1016 lines
26 KiB
JavaScript
Executable File
// parseUri 1.2.2
|
|
// (c) Steven Levithan <stevenlevithan.com>
|
|
// MIT License
|
|
// http://blog.stevenlevithan.com/archives/parseuri
|
|
function parseUri (str) {
|
|
var o = parseUri.options,
|
|
m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
|
|
uri = {},
|
|
i = 14;
|
|
|
|
while (i--) uri[o.key[i]] = m[i] || "";
|
|
|
|
uri[o.q.name] = {};
|
|
uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
|
|
if ($1) uri[o.q.name][$1] = $2;
|
|
});
|
|
|
|
return uri;
|
|
};
|
|
|
|
parseUri.options = {
|
|
strictMode: false,
|
|
key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
|
|
q: {
|
|
name: "queryKey",
|
|
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
|
|
},
|
|
parser: {
|
|
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
|
|
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
|
|
}
|
|
};
|
|
|
|
if (!String.prototype.trim) {
|
|
String.prototype.trim = function() {
|
|
return this.replace(/^\s+|\s+$/g, "");
|
|
};
|
|
}
|
|
|
|
// Array Remove - By John Resig (MIT Licensed)
|
|
// http://stackoverflow.com/questions/500606/javascript-array-delete-elements
|
|
Array.prototype.remove = function(from, to) {
|
|
var rest = this.slice((to || from) + 1 || this.length);
|
|
this.length = from < 0 ? this.length + from : from;
|
|
return this.push.apply(this, rest);
|
|
};
|
|
|
|
Element.prototype.removeAllChilds = function() {
|
|
while (this.hasChildNodes())
|
|
this.removeChild(this.childNodes[0]);
|
|
};
|
|
|
|
function _generate_random(size, symbols)
|
|
{
|
|
forbidden = new Array('\\');
|
|
|
|
var res = "";
|
|
while (res.length < size)
|
|
{
|
|
a = Math.floor(Math.random() * (symbols.length/2)) * 2;
|
|
diff = symbols[a+1] - symbols[a];
|
|
r = Math.floor(Math.random()*diff);
|
|
if (isNaN(r+symbols[a]))
|
|
continue;
|
|
character = String.fromCharCode(r + symbols[a]);
|
|
forbid = false;
|
|
for (var j=0; j<forbidden.length; j++)
|
|
{
|
|
if (character == forbidden[j])
|
|
{
|
|
forbid = true;
|
|
break;
|
|
}
|
|
}
|
|
if (forbid) continue;
|
|
res += character;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
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)
|
|
var symbols;
|
|
if (only_ascii)
|
|
symbols = new Array(32, 47, 48, 57, 58, 64, 91, 96, 123, 126, 65, 90, 97, 122, 65, 90, 97, 122, 48, 57);
|
|
else
|
|
symbols = new Array(1, 255);
|
|
|
|
return _generate_random(size, symbols);
|
|
}
|
|
|
|
function generate_password()
|
|
{
|
|
document.getElementById("new_password").value = generate_random(16, true);
|
|
}
|
|
|
|
function generate_simple_password()
|
|
{
|
|
// ! ( ) * + - . _
|
|
// numbers 48 - 57
|
|
// upper 65 - 90
|
|
// lower 97 - 122
|
|
symbols = new Array(33, 33, 40, 43, 45, 46, 95, 95, 48, 57, 65, 90, 97, 122, 48, 57, 65, 90, 97, 122, 48, 57, 48, 57, 65, 90, 97, 122);
|
|
document.getElementById("new_password").value = _generate_random(8, symbols);
|
|
}
|
|
|
|
function clear_form()
|
|
{
|
|
div = document.getElementById("add_new_password");
|
|
|
|
inputs = div.getElementsByTagName("input");
|
|
|
|
for(i=0; i<inputs.length; i++)
|
|
{
|
|
if (inputs[i].type == "text" ||
|
|
inputs[i].type == "password")
|
|
inputs[i].value = "";
|
|
}
|
|
chkPass("");
|
|
}
|
|
|
|
function url_domain(data) {
|
|
var uri = parseUri(data)
|
|
return uri['host'];
|
|
}
|
|
|
|
// http://stackoverflow.com/questions/3745666/how-to-convert-from-hex-to-ascii-in-javascript
|
|
function hex2a(hex) {
|
|
var str = '';
|
|
for (var i = 0; i < hex.length; i += 2)
|
|
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
|
|
return str;
|
|
}
|
|
|
|
function a2hex(str) {
|
|
var hex = '';
|
|
for (var i = 0; i < str.length; i++)
|
|
{
|
|
c = str.charCodeAt(i).toString(16);
|
|
if (c.length == 1) c = "0" + c;
|
|
hex += c;
|
|
}
|
|
return hex;
|
|
}
|
|
|
|
async function derive_mkey(user, mkey)
|
|
{
|
|
url = url_domain(server_url) + "/" + user;
|
|
global_iv = simple_pbkdf2(url, mkey, pbkdf2_level);
|
|
return crypto_pbkdf2(mkey, url, pbkdf2_level);
|
|
}
|
|
|
|
var passwords = null;
|
|
var current_user = "";
|
|
var current_mkey = "";
|
|
var clearTimer = null;
|
|
var global_iv = null;
|
|
var server_url = window.location.href.split('?')[0];
|
|
|
|
function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
|
|
this.ciphered_login = ciphered_login;
|
|
this.ciphered_password = ciphered_password;
|
|
this.unciphered = false;
|
|
this.clear_url = "";
|
|
this.clear_login = "";
|
|
this.clear_password = "";
|
|
this.masterkey = null;
|
|
this.salt = salt;
|
|
this.shadow_login = shadow_login;
|
|
this.access_token = "";
|
|
|
|
this.reset = function()
|
|
{
|
|
this.unciphered = false;
|
|
this.clear_url = "";
|
|
this.clear_login = "";
|
|
this.clear_password = "";
|
|
this.masterkey = null;
|
|
this.salt = salt;
|
|
}
|
|
|
|
this.reset_master_key = function()
|
|
{
|
|
this.masterkey = null;
|
|
}
|
|
|
|
this.encrypt = async function(masterkey)
|
|
{
|
|
if (masterkey == this.masterkey)
|
|
return true;
|
|
|
|
if (masterkey == null || this.clear_url == "" || this.clear_login == "")
|
|
return false;
|
|
|
|
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
|
|
var ciphered_password = generate_random(3, false) + this.clear_password ;
|
|
|
|
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)
|
|
await this.generate_access_token(masterkey);
|
|
}
|
|
|
|
this.decrypt = async function(masterkey)
|
|
{
|
|
if (masterkey == null)
|
|
return false;
|
|
|
|
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)
|
|
{
|
|
login = login.slice(0, login.length-16).replace(/\0*$/, "");
|
|
}
|
|
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];
|
|
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;
|
|
|
|
return true;
|
|
}
|
|
|
|
this.isUnciphered = function(masterkey)
|
|
{
|
|
return (this.unciphered == true && masterkey == this.masterkey && masterkey != null)
|
|
}
|
|
|
|
this.isCiphered = function(masterkey)
|
|
{
|
|
return !(this.isUnciphered(masterkey));
|
|
}
|
|
|
|
this.shadow_login_to_access_token = async function(masterkey)
|
|
{
|
|
this.access_token = await encrypt_ecb(masterkey, hex2a(this.shadow_login));
|
|
this.access_token = a2hex(this.access_token);
|
|
}
|
|
|
|
this.generate_access_token = async function(masterkey)
|
|
{
|
|
this.salt = a2hex(generate_random(16, false));
|
|
this.shadow_login = a2hex(generate_random(16, false));
|
|
|
|
return await this.shadow_login_to_access_token(masterkey);
|
|
}
|
|
}
|
|
|
|
function clearMasterKey()
|
|
{
|
|
current_mkey = null;
|
|
|
|
for(i=0; i<passwords.length; i++)
|
|
{
|
|
passwords[i].reset();
|
|
}
|
|
}
|
|
|
|
function stopClearTimer()
|
|
{
|
|
if (clearTimer)
|
|
{
|
|
clearTimeout(clearTimer);
|
|
clearTimer = null;
|
|
}
|
|
}
|
|
|
|
function startClearTimer()
|
|
{
|
|
stopClearTimer();
|
|
clearTimer = setTimeout(
|
|
function()
|
|
{
|
|
clearMasterKey();
|
|
change_master_key(false);
|
|
scrollToTop();
|
|
}
|
|
, CLEAR_TIME);
|
|
}
|
|
|
|
function list_all_entries(user)
|
|
{
|
|
passwords = new Array();
|
|
|
|
req = new XMLHttpRequest();
|
|
req.addEventListener("load", function(evt) {
|
|
j = JSON.parse(this.responseText);
|
|
for(i=0; i<j.entries.length; i++)
|
|
{
|
|
if (j.entries[i].hasOwnProperty('login'))
|
|
p = new PasswordEntry(j.entries[i].login, j.entries[i].password, "", "");
|
|
else
|
|
p = new PasswordEntry("", "", j.entries[i].salt, j.entries[i].shadow_login);
|
|
passwords.push(p);
|
|
}
|
|
}
|
|
, 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);
|
|
}
|
|
|
|
function update_stats()
|
|
{
|
|
nb_ciphered_passwords = 0;
|
|
nb_unciphered_passwords = 0;
|
|
|
|
for(i=0; i<passwords.length; i++)
|
|
{
|
|
if (passwords[i].isUnciphered(current_mkey))
|
|
nb_unciphered_passwords++;
|
|
else
|
|
nb_ciphered_passwords++;
|
|
}
|
|
|
|
div = document.getElementById("nb_unciphered_passwords");
|
|
div.removeAllChilds();
|
|
|
|
text = document.createElement("b");
|
|
text.appendChild(document.createTextNode(nb_unciphered_passwords + " unciphered password(s)"));
|
|
div.appendChild(text);
|
|
div.appendChild(document.createElement("br"));
|
|
div.appendChild(document.createElement("br"));
|
|
|
|
div = document.getElementById("nb_ciphered_passwords");
|
|
div.removeAllChilds();
|
|
|
|
text = document.createElement("b");
|
|
text.appendChild(document.createTextNode(nb_ciphered_passwords + " ciphered password(s)"));
|
|
div.appendChild(text);
|
|
div.appendChild(document.createElement("br"));
|
|
div.appendChild(document.createElement("br"));
|
|
}
|
|
|
|
// Remove all password without credentials
|
|
async function put_ciphered_credentials(passwords, masterkey)
|
|
{
|
|
for(var i=0; i<passwords.length; i++)
|
|
{
|
|
await passwords[i].generate_access_token(masterkey);
|
|
remove_password_server(current_user, passwords[i].ciphered_login, '');
|
|
add_password_server(current_user, passwords[i]);
|
|
}
|
|
}
|
|
|
|
async function get_ciphered_credentials(masterkey)
|
|
{
|
|
access_tokens = '';
|
|
old_passwords = new Array();
|
|
|
|
for(var i=0; i<passwords.length; i++)
|
|
{
|
|
// Already got
|
|
if (passwords[i].ciphered_login.length)
|
|
{
|
|
if (!passwords[i].access_token.length)
|
|
{
|
|
res = await passwords[i].decrypt(masterkey);
|
|
if(res)
|
|
old_passwords.push(passwords[i]);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
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)
|
|
await put_ciphered_credentials(old_passwords, masterkey);
|
|
|
|
if (!access_tokens.length)
|
|
return;
|
|
|
|
req = new XMLHttpRequest();
|
|
req.addEventListener("load", function(evt) {
|
|
j = JSON.parse(this.responseText);
|
|
for(i=0; i<j.entries.length; i++)
|
|
{
|
|
for (k=0; k<passwords.length; k++)
|
|
{
|
|
if (passwords[k].access_token == j.entries[i].access_token)
|
|
{
|
|
passwords[k].ciphered_login = j.entries[i].login;
|
|
passwords[k].ciphered_password = j.entries[i].password;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}, 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=" + current_user + "&access_tokens=" + access_tokens);
|
|
}
|
|
|
|
async function change_master_key(warning_unciphered)
|
|
{
|
|
var nb_unciphered = 0;
|
|
|
|
if (current_mkey && use_shadow_logins)
|
|
await get_ciphered_credentials(current_mkey);
|
|
|
|
for(i=0; i<passwords.length; i++)
|
|
{
|
|
if (await passwords[i].decrypt(current_mkey))
|
|
nb_unciphered++;
|
|
}
|
|
|
|
if (!nb_unciphered && warning_unciphered)
|
|
alert("No password unciphered with this master key !");
|
|
|
|
password_div = document.getElementById("passwords");
|
|
password_div.removeAllChilds();
|
|
|
|
div = document.createElement("div");
|
|
div.setAttribute("id", "nb_unciphered_passwords");
|
|
password_div.appendChild(div);
|
|
|
|
for(i=0; i<passwords.length; i++)
|
|
{
|
|
if (passwords[i].isUnciphered(current_mkey))
|
|
{
|
|
div = document.createElement("div");
|
|
div.setAttribute("id", "unciph_entry_" + i);
|
|
div.setAttribute("class", "password");
|
|
|
|
ciph_login = document.createElement("input");
|
|
ciph_login.setAttribute("name", "ciphered_login");
|
|
ciph_login.setAttribute("type", "hidden");
|
|
ciph_login.setAttribute("login", passwords[i].ciphered_login);
|
|
div.appendChild(ciph_login);
|
|
|
|
div.appendChild(document.createTextNode("URL"));
|
|
url = document.createElement("input");
|
|
url.setAttribute("type", "text");
|
|
url.setAttribute("name", "url");
|
|
url.setAttribute("value", passwords[i].clear_url);
|
|
div.appendChild(url);
|
|
|
|
div.appendChild(document.createTextNode("login"));
|
|
login = document.createElement("input");
|
|
login.setAttribute("type", "text");
|
|
login.setAttribute("name", "login");
|
|
login.setAttribute("value", passwords[i].clear_login);
|
|
div.appendChild(login);
|
|
|
|
div.appendChild(document.createTextNode("password"));
|
|
password = document.createElement("input");
|
|
password.setAttribute("type", "text");
|
|
password.setAttribute("name", "password");
|
|
password.setAttribute("value", passwords[i].clear_password);
|
|
div.appendChild(password);
|
|
|
|
delete_button = document.createElement("input");
|
|
delete_button.setAttribute("type", "button");
|
|
delete_button.setAttribute("value", "Delete");
|
|
delete_button.setAttribute("onclick", "delete_entry(\"unciph_entry_" + i + "\");");
|
|
div.appendChild(delete_button);
|
|
|
|
update_button = document.createElement("input");
|
|
update_button.setAttribute("type", "button");
|
|
update_button.setAttribute("value", "Update");
|
|
update_button.setAttribute("onclick", "update_entry(\"unciph_entry_" + i + "\");");
|
|
div.appendChild(update_button);
|
|
|
|
password_div.appendChild(div);
|
|
}
|
|
}
|
|
|
|
div = document.createElement("div");
|
|
div.setAttribute("id", "nb_ciphered_passwords");
|
|
password_div.appendChild(div);
|
|
|
|
for(i=0; i<passwords.length; i++)
|
|
{
|
|
if (passwords[i].isCiphered(current_mkey))
|
|
{
|
|
div = document.createElement("div");
|
|
div.setAttribute("id", "ciph_entry_" + i);
|
|
div.setAttribute("class", "password");
|
|
|
|
ciph_login = document.createElement("input");
|
|
ciph_login.setAttribute("name", "ciphered_login");
|
|
ciph_login.setAttribute("type", "hidden");
|
|
div.appendChild(ciph_login);
|
|
|
|
div.appendChild(document.createTextNode("URL"));
|
|
url = document.createElement("input");
|
|
url.setAttribute("class", "hash");
|
|
url.setAttribute("type", "text");
|
|
url.setAttribute("name", "URL");
|
|
div.appendChild(url);
|
|
|
|
div.appendChild(document.createTextNode("password"));
|
|
password = document.createElement("input");
|
|
password.setAttribute("class", "hash");
|
|
password.setAttribute("type", "text");
|
|
password.setAttribute("name", "password");
|
|
div.appendChild(password);
|
|
|
|
delete_button = document.createElement("input");
|
|
delete_button.setAttribute("type", "button");
|
|
delete_button.setAttribute("value", "Delete");
|
|
delete_button.setAttribute("onclick", "delete_entry(\"ciph_entry_" + i + "\");");
|
|
div.appendChild(delete_button);
|
|
|
|
password_div.appendChild(div);
|
|
|
|
if (passwords[i].ciphered_login.length)
|
|
{
|
|
ciph_login.setAttribute("login", passwords[i].ciphered_login);
|
|
url.setAttribute("value", passwords[i].ciphered_login);
|
|
password.setAttribute("value", passwords[i].ciphered_password);
|
|
}
|
|
else
|
|
{
|
|
ciph_login.setAttribute("login", passwords[i].shadow_login);
|
|
url.setAttribute("value", passwords[i].shadow_login);
|
|
// password empty
|
|
}
|
|
}
|
|
}
|
|
|
|
input = document.getElementById("master_key");
|
|
input.value = "";
|
|
|
|
update_stats();
|
|
}
|
|
|
|
function update_master_key(warning_unciphered)
|
|
{
|
|
user = select_widget.options[select_widget.selectedIndex].value;
|
|
|
|
if (user != current_user)
|
|
{
|
|
current_user = user;
|
|
|
|
document.title = "gPass - " + current_user;
|
|
|
|
list_all_entries(current_user);
|
|
|
|
addon_address = document.getElementById("addon_address");
|
|
addon_address.removeAllChilds();
|
|
|
|
addon_address.appendChild(document.createTextNode("Current addon address is : " + server_url + current_user));
|
|
|
|
warning_unciphered = false;
|
|
}
|
|
|
|
current_mkey = document.getElementById("master_key").value;
|
|
|
|
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();
|
|
clearMasterKey();
|
|
}
|
|
|
|
change_master_key(warning_unciphered);
|
|
}
|
|
|
|
function start()
|
|
{
|
|
select_widget = document.getElementById('selected_user') ;
|
|
|
|
if (select_widget == null) return;
|
|
|
|
return update_master_key(false);
|
|
}
|
|
|
|
function add_password_server(user, pentry)
|
|
{
|
|
var ok = false;
|
|
req = new XMLHttpRequest();
|
|
req.addEventListener("load", function(evt) {
|
|
resp = this.responseText;
|
|
if (resp == "OK")
|
|
ok = true;
|
|
else
|
|
alert(resp);
|
|
}, 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;
|
|
}
|
|
|
|
async function construct_pentry(user, url, password, login, mkey, derive_masterkey)
|
|
{
|
|
var ret = null;
|
|
|
|
if (url == "")
|
|
{
|
|
alert("URL is empty");
|
|
return ret;
|
|
}
|
|
|
|
if (login == "")
|
|
{
|
|
alert("Login is empty");
|
|
return ret;
|
|
}
|
|
|
|
if (password == "")
|
|
{
|
|
alert("Password is empty");
|
|
return ret;
|
|
}
|
|
|
|
if (mkey == "")
|
|
{
|
|
alert("Master key is empty");
|
|
return ret;
|
|
}
|
|
|
|
if (derive_masterkey)
|
|
mkey = derive_mkey(user, mkey);
|
|
|
|
for(i=0; i<passwords.length; i++)
|
|
{
|
|
p = passwords[i];
|
|
if (p.clear_url == url &&
|
|
p.clear_password == password &&
|
|
p.clear_login == login &&
|
|
p.masterkey == mkey)
|
|
{
|
|
alert("Entry already exists");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
pentry = new PasswordEntry("", "", "", "");
|
|
pentry.clear_url = url;
|
|
pentry.clear_login = login;
|
|
pentry.clear_password = password;
|
|
await pentry.encrypt(mkey);
|
|
|
|
return pentry;
|
|
}
|
|
|
|
function remove_password_server(user, login, access_token)
|
|
{
|
|
var ok = false;
|
|
|
|
req = new XMLHttpRequest();
|
|
req.addEventListener("load", function(evt) {
|
|
resp = this.responseText;
|
|
if (resp == "OK")
|
|
ok = true;
|
|
else
|
|
alert(resp);
|
|
}, 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);
|
|
|
|
return ok;
|
|
}
|
|
|
|
function add_password()
|
|
{
|
|
var url = "";
|
|
var login = "";
|
|
var password = "";
|
|
var mkey = "";
|
|
|
|
div = document.getElementById("add_new_password");
|
|
|
|
inputs = div.getElementsByTagName("input");
|
|
|
|
for(i=0; i<inputs.length; i++)
|
|
{
|
|
if (inputs[i].getAttribute("name") == "url")
|
|
url = url_domain(inputs[i].value);
|
|
else if (inputs[i].getAttribute("name") == "login")
|
|
login = inputs[i].value.trim();
|
|
else if (inputs[i].getAttribute("name") == "password")
|
|
password = inputs[i].value.trim();
|
|
else if (inputs[i].getAttribute("name") == "mkey")
|
|
mkey = inputs[i].value;
|
|
}
|
|
|
|
construct_pentry(current_user, url, password, login, mkey, true).then(
|
|
function (pentry) {
|
|
if (pentry == null) return false;
|
|
|
|
res = add_password_server(current_user, pentry);
|
|
|
|
if (!res) return false;
|
|
|
|
for(i=0; i<passwords.length; i++)
|
|
{
|
|
passwords[i].reset_master_key();
|
|
}
|
|
|
|
passwords.push(pentry);
|
|
|
|
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 = "";
|
|
}
|
|
|
|
startClearTimer();
|
|
});
|
|
|
|
window.scrollTo(0,document.body.scrollHeight);
|
|
|
|
return true;
|
|
}
|
|
|
|
function delete_entry(entry_number)
|
|
{
|
|
startClearTimer();
|
|
|
|
entry = document.getElementById(entry_number);
|
|
|
|
if (entry == null) {
|
|
alert(entry_number + " not found");
|
|
return;
|
|
}
|
|
|
|
inputs = entry.getElementsByTagName("input");
|
|
|
|
var ciphered_login = null;
|
|
for(i=0; i<inputs.length; i++)
|
|
{
|
|
if (inputs[i].getAttribute("name") == "ciphered_login")
|
|
{
|
|
ciphered_login = inputs[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ciphered_login == null)
|
|
{
|
|
alert("Widget not found");
|
|
return;
|
|
}
|
|
|
|
var found = -1;
|
|
for(i=0; i<passwords.length; i++)
|
|
{
|
|
if (passwords[i].ciphered_login == ciphered_login.getAttribute("login") ||
|
|
passwords[i].shadow_login == ciphered_login.getAttribute("login"))
|
|
{
|
|
found = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found == -1)
|
|
{
|
|
alert("Password not found int database");
|
|
return;
|
|
}
|
|
|
|
if(!confirm("Are you sure want to delete this entry ?"))
|
|
return;
|
|
|
|
ok = remove_password_server(current_user, ciphered_login.getAttribute("login"), passwords[i].access_token);
|
|
|
|
if (!ok) return;
|
|
|
|
parent = ciphered_login.parentNode;
|
|
parent.removeAllChilds();
|
|
|
|
passwords.remove(found);
|
|
|
|
update_stats();
|
|
}
|
|
|
|
function update_entry(entry_number)
|
|
{
|
|
var url = "";
|
|
var login = "";
|
|
var password = "";
|
|
var mkey = "";
|
|
var ciphered_login;
|
|
|
|
startClearTimer();
|
|
|
|
entry = document.getElementById(entry_number);
|
|
|
|
if (entry == null) {
|
|
alert(entry_number + " not found");
|
|
return;
|
|
}
|
|
|
|
inputs = entry.getElementsByTagName("input");
|
|
|
|
var ciphered_login = null;
|
|
for(i=0; i<inputs.length; i++)
|
|
{
|
|
if (inputs[i].getAttribute("name") == "url")
|
|
url = url_domain(inputs[i].value);
|
|
else if (inputs[i].getAttribute("name") == "login")
|
|
login = inputs[i].value.trim();
|
|
else if (inputs[i].getAttribute("name") == "password")
|
|
password = inputs[i].value.trim();
|
|
else if (inputs[i].getAttribute("name") == "ciphered_login")
|
|
ciphered_login = inputs[i];
|
|
}
|
|
|
|
var found = -1;
|
|
for(i=0; i<passwords.length; i++)
|
|
{
|
|
if (passwords[i].ciphered_login == ciphered_login.getAttribute("login"))
|
|
{
|
|
found = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found == -1)
|
|
{
|
|
alert("Password not found int database");
|
|
return;
|
|
}
|
|
|
|
if(!confirm("Are you sure want to update this entry ?"))
|
|
return;
|
|
|
|
construct_pentry(current_user, url, password, login, current_mkey, false).then(
|
|
function (pentry) {
|
|
if (pentry == null) 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;
|
|
|
|
passwords[found] = pentry;
|
|
ciphered_login.setAttribute("login", pentry.ciphered_login);
|
|
|
|
alert("Entry updated");
|
|
});
|
|
}
|
|
|
|
async function update_masterkey()
|
|
{
|
|
var url = "";
|
|
var login = "";
|
|
var password = "";
|
|
var mkey = "";
|
|
var ciphered_login;
|
|
|
|
oldmkey = document.getElementById("oldmkey").value;
|
|
newmkey = document.getElementById("newmkey").value;
|
|
|
|
if (newmkey == "" || oldmkey == "")
|
|
{
|
|
alert("Cannot set an empty masterkey");
|
|
return;
|
|
}
|
|
|
|
if(!confirm("Are you sure want to update the 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++)
|
|
{
|
|
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)
|
|
{
|
|
alert("Error updating password");
|
|
break;
|
|
}
|
|
|
|
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)
|
|
{
|
|
alert("Error updating password");
|
|
break;
|
|
}
|
|
found++;
|
|
}
|
|
}
|
|
|
|
if (found == 0)
|
|
alert("No password found with this masterkey");
|
|
else
|
|
{
|
|
alert(found + " passwords updated");
|
|
change_master_key(false);
|
|
}
|
|
}
|
|
|
|
function makeText(text) {
|
|
var data = new Blob([text], {type: 'application/xml'});
|
|
|
|
textFile = window.URL.createObjectURL(data);
|
|
|
|
// returns a URL you can use as a href
|
|
return textFile;
|
|
};
|
|
|
|
var text_link = null;
|
|
function export_database()
|
|
{
|
|
startClearTimer();
|
|
|
|
link = document.getElementById("export_link");
|
|
|
|
if (text_link != null) window.URL.revokeObjectURL(text_link);
|
|
|
|
text = "<passwords user=\"" + current_user + "\" addon_address=\"" + server_url + current_user + "\">\n";
|
|
for(i=0; i<passwords.length; i++)
|
|
{
|
|
if (!passwords[i].unciphered) continue;
|
|
text += "\t<password_entry>\n"
|
|
text += "\t\t<url value=\"" + passwords[i].clear_url + "\"/>\n";
|
|
text += "\t\t<login value=\"" + passwords[i].clear_login + "\"/>\n";
|
|
text += "\t\t<password><![CDATA[" + passwords[i].clear_password.replace("]]>", "]]\\>", "g") + "]]></password>\n";
|
|
text += "\t</password_entry>\n"
|
|
}
|
|
text += "</passwords>\n";
|
|
|
|
text_link = makeText(text);
|
|
link.href = text_link;
|
|
|
|
link.style.display = "inline";
|
|
link.style.visibility = "visible";
|
|
|
|
alert_msg = "Click on download link to get all current unciphered passwords\n\n";
|
|
alert_msg += "\"]]>\" sequence has been replaced by \"]]\\>\"";
|
|
alert(alert_msg);
|
|
}
|