gPass/server/resources/gpass.js

658 lines
17 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_password()
{
// 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 res = "";
while (res.length < 16)
{
a = Math.round(Math.random() * (symbols.length/2) * 2);
diff = symbols[a+1] - symbols[a];
r = Math.round(Math.random()*diff);
if (isNaN(r+symbols[a]))
continue;
res += String.fromCharCode(r + symbols[a]);
}
field.value = res;
}
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;
}
function derive_mkey(user, mkey)
{
url = url_domain(document.URL) + "/" + user;
mkey = a2hex(pkdbf2(mkey, url, pkdbf2_level, 256/8));
return mkey;
}
var passwords;
var current_user = "";
var current_mkey = "";
function PasswordEntry (ciphered_login, ciphered_password) {
this.ciphered_login = ciphered_login;
this.ciphered_password = ciphered_password;
this.unciphered = false;
this.clear_url = "";
this.clear_login = "";
this.clear_password = "";
this.masterkey = "";
this.decrypt = function(masterkey)
{
if (masterkey == this.masterkey && this.unciphered == true)
return true;
if (masterkey == "" || this.unciphered == true)
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)
{
aes.finish();
return false;
}
// Remove @@
login = login.substring(2);
infos = login.split(";");
this.clear_url = infos[0];
this.clear_login = infos[1];
this.clear_password = aes.decryptLongString(hex2a(this.ciphered_password), a_masterkey);
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 != "")
}
this.isCiphered = function(masterkey)
{
return !(this.isUnciphered(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")
{
for(i=1; i<entries.length; i++)
{
if (entries[i] == "") continue;
entry = entries[i].split(";");
passwords.push(new PasswordEntry(entry[0], entry[1]));
}
}
}, false);
req.open("POST", document.documentURI, 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"));
}
function change_master_key(warning_unciphered)
{
var nb_unciphered = 0;
for(i=0; i<passwords.length; i++)
{
if (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");
ciph_login.setAttribute("login", passwords[i].ciphered_login);
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");
url.setAttribute("value", passwords[i].ciphered_login);
div.appendChild(url);
div.appendChild(document.createTextNode("password"));
password = document.createElement("input");
password.setAttribute("class", "hash");
password.setAttribute("type", "text");
password.setAttribute("name", "password");
password.setAttribute("value", passwords[i].ciphered_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);
}
}
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 : " + document.documentURI + current_user));
warning_unciphered = false;
}
current_mkey = document.getElementById("master_key").value;
if (current_mkey != "")
current_mkey = derive_mkey(current_user, current_mkey);
else
// Disable warning on empty master key (clear passwords from others)
warning_unciphered = false;
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", document.documentURI, 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);
return ok;
}
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(current_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;
}
}
ciphered_login = "@@" + url + ";" + login;
// Add salt
for(i=0; i<3; i++)
{
password += String.fromCharCode((Math.random() * 128)+1);
}
ciphered_password = password;
aes = new AES();
a_masterkey = aes.init(hex2a(mkey));
ciphered_login = a2hex(aes.encryptLongString(ciphered_login, a_masterkey));
ciphered_password = a2hex(aes.encryptLongString(ciphered_password, a_masterkey));
pentry = new PasswordEntry(ciphered_login, ciphered_password);
pentry.unciphered = true;
pentry.clear_url = url;
pentry.clear_login = login;
pentry.clear_password = password.substr(0, password.length-3);
pentry.masterkey = mkey;
return pentry;
}
function remove_password_server(user, login)
{
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", document.documentURI, false);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
req.send("delete_entry=1&user=" + user + "&login=" + login);
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;
}
pentry = construct_pentry(current_user, url, password, login, mkey, true)
if (pentry == null) return;
res = add_password_server(current_user, pentry);
if (!res) return false;
current_mkey = pentry.masterkey;
passwords.push(pentry);
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 = "";
}
return true;
}
function delete_entry(entry_number)
{
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"))
{
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"));
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;
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;
pentry = construct_pentry(current_user, url, password, login, current_mkey, false);
if (pentry == null) return;
ok = remove_password_server(current_user, passwords[found].ciphered_login);
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");
}