// 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 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 = document.documentURI; 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(); }); 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); }