537 lines
13 KiB
JavaScript
537 lines
13 KiB
JavaScript
/*
|
|
Copyright (C) 2013-2020 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 browser = browser || chrome;
|
|
var protocol_version = 4;
|
|
var account_url = null;
|
|
|
|
function _notification(message, data)
|
|
{
|
|
if (message !== data)
|
|
message += data;
|
|
|
|
options = {
|
|
type: "basic",
|
|
title : "gPass",
|
|
message : message,
|
|
iconUrl:browser.extension.getURL("icons/gpass_icon_64.png")
|
|
};
|
|
|
|
browser.notifications.create("gPass", options, function(){});
|
|
|
|
window.setTimeout(function() {browser.notifications.clear("gPass", function(){})}, 2000);
|
|
}
|
|
|
|
async function generate_request(domain, login, mkey, iv)
|
|
{
|
|
var v = domain + ";" + login;
|
|
debug("will encrypt " + v);
|
|
while ((v.length % 16))
|
|
v += "\0";
|
|
hash = await digest(v);
|
|
v += hash.slice(8, 24);
|
|
enc = encrypt_cbc(mkey, iv, v);
|
|
|
|
return enc;
|
|
}
|
|
|
|
async function ask_server(logins, domain, wdomain, mkey, sendResponse, options)
|
|
{
|
|
account_url = await get_preference("account_url");
|
|
|
|
var salt = parseURI.parseUri(account_url);
|
|
salt = salt["host"] + salt["path"];
|
|
|
|
debug("salt " + salt);
|
|
|
|
pbkdf2_level = await get_preference("pbkdf2_level");
|
|
|
|
global_iv = await simple_pbkdf2(salt, mkey, pbkdf2_level);
|
|
global_iv = global_iv.slice(0, 16);
|
|
mkey = crypto_pbkdf2(mkey, salt, pbkdf2_level);
|
|
|
|
debug("global_iv " + a2hex(global_iv));
|
|
|
|
keys = "";
|
|
for(key_index=0, a=0; a<logins.length; a++, key_index++)
|
|
{
|
|
enc = await generate_request(domain, logins[a], mkey, global_iv);
|
|
keys += (keys.length != 0) ? "&" : "";
|
|
keys += "k" + key_index + "=" + a2hex(enc);
|
|
|
|
if (wdomain != "")
|
|
{
|
|
enc = await generate_request(wdomain, logins[a], mkey, global_iv);
|
|
keys += (keys.length != 0) ? "&" : "";
|
|
keys += "k" + (++key_index) + "=" + a2hex(enc);
|
|
}
|
|
}
|
|
|
|
debug("Keys " + keys);
|
|
|
|
var gPassRequest = new XMLHttpRequest();
|
|
|
|
var ret = SERVER.OK;
|
|
|
|
// gPassRequest.addEventListener("progress", function(evt) { ; }, false);
|
|
gPassRequest.addEventListener("load", async function(evt) {
|
|
var ciphered_password = "";
|
|
var clear_password = "";
|
|
var server_pbkdf2_level = 0;
|
|
var server_version = 0;
|
|
var matched_key = 0;
|
|
|
|
var r = this.responseText.split("\n");
|
|
debug("resp " + r);
|
|
|
|
for(var a=0; a<r.length; a++)
|
|
{
|
|
debug("Analyse " + r[a]);
|
|
|
|
params = r[a].split("=");
|
|
if (params.length != 2 && params[0] != "<end>")
|
|
{
|
|
_notification("Error : It seems that it's not a gPass server",
|
|
this.responseText);
|
|
ret = SERVER.FAILED;
|
|
break;
|
|
}
|
|
|
|
switch(params[0])
|
|
{
|
|
case "protocol":
|
|
debug("protocol : " + params[1]);
|
|
|
|
if (params[1].indexOf("gpass-") != 0)
|
|
{
|
|
_notification("Error : It seems that it's not a gPass server",
|
|
this.responseText);
|
|
ret = SERVER.FAILED;
|
|
break;
|
|
}
|
|
|
|
server_protocol_version = params[1].match(/\d+/)[0];
|
|
|
|
if (server_protocol_version > protocol_version)
|
|
{
|
|
_notification("Protocol version not supported, please upgrade your addon", "");
|
|
ret = SERVER.FAILED;
|
|
}
|
|
else
|
|
{
|
|
switch (server_protocol_version)
|
|
{
|
|
case 2:
|
|
server_pbkdf2_level = 1000;
|
|
break;
|
|
case 3:
|
|
// Version 3 : nothing special to do
|
|
case 4:
|
|
// Version 4 : nothing special to do
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case "matched_key":
|
|
matched_key = params[1];
|
|
case "pass":
|
|
ciphered_password = params[1];
|
|
break;
|
|
case "pkdbf2_level":
|
|
case "pbkdf2_level":
|
|
server_pbkdf2_level = parseInt(params[1].match(/\d+/)[0], 10);
|
|
if (server_pbkdf2_level != NaN &&
|
|
server_pbkdf2_level != pbkdf2_level &&
|
|
server_pbkdf2_level >= 1000) // Minimum level for PBKDF2 !
|
|
{
|
|
debug("New pbkdf2 level " + server_pbkdf2_level);
|
|
pbkdf2_level = server_pbkdf2_level;
|
|
set_preference("pbkdf2_level", pbkdf2_level, null);
|
|
ret = SERVER.RESTART_REQUEST;
|
|
}
|
|
break;
|
|
case "<end>":
|
|
break;
|
|
default:
|
|
debug("Unknown command " + params[0]);
|
|
|
|
_notification("Error : It seems that it's not a gPass server",
|
|
this.responseText);
|
|
ret = SERVER.FAILED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret != SERVER.OK)
|
|
{
|
|
sendResponse({"value": ret, options:options});
|
|
return;
|
|
}
|
|
|
|
if (ciphered_password != "")
|
|
{
|
|
debug("Ciphered password : " + ciphered_password);
|
|
clear_password = await decrypt_cbc(mkey, global_iv, hex2a(ciphered_password));
|
|
clear_password = clear_password.replace(/\0*$/, "");
|
|
clear_password = clear_password.substr(3, clear_password.length);
|
|
debug("Clear password " + clear_password);
|
|
}
|
|
else
|
|
{
|
|
debug("No password found");
|
|
|
|
ret = SERVER.FAILED;
|
|
|
|
_notification("No password found in database", "")
|
|
}
|
|
|
|
sendResponse({"value": ret, "password":clear_password, "options":options});
|
|
}, false);
|
|
gPassRequest.addEventListener("error", function(evt) {
|
|
debug("error");
|
|
ret = false;
|
|
_notification("Error");
|
|
}, false);
|
|
debug("connect to " + account_url);
|
|
gPassRequest.open("POST", account_url, true);
|
|
gPassRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
|
|
gPassRequest.send(keys);
|
|
|
|
return true;
|
|
}
|
|
|
|
function update_gpass_icon(iconId, tabId)
|
|
{
|
|
debug("update_gpass_icon");
|
|
|
|
icon_infos = {"tabId":tabId};
|
|
icon_name = "";
|
|
|
|
switch (iconId)
|
|
{
|
|
case GPASS_ICON.NORMAL: break;
|
|
case GPASS_ICON.DISABLED:
|
|
icon_name = "_disabled";
|
|
break;
|
|
case GPASS_ICON.ACTIVATED:
|
|
icon_name = "_activated";
|
|
break;
|
|
default:
|
|
}
|
|
|
|
debug(icon_name);
|
|
|
|
icon_infos["path"] = {
|
|
16:"icons/gpass" + icon_name + "_icon_16.png",
|
|
32:"icons/gpass" + icon_name + "_icon_32.png",
|
|
64:"icons/gpass" + icon_name + "_icon_64.png",
|
|
128:"icons/gpass" + icon_name + "_icon_128.png",
|
|
};
|
|
|
|
browser.browserAction.setIcon(icon_infos);
|
|
}
|
|
|
|
async function is_gpass_enabled(uri)
|
|
{
|
|
return await get_preference("always_disabled").then(
|
|
function(always_disabled) {
|
|
if (always_disabled)
|
|
{
|
|
debug("Always disabled");
|
|
return new Promise(function(resolve, reject) {
|
|
resolve(1); // null -> enabled, 1 -> disabled
|
|
});
|
|
|
|
}
|
|
else
|
|
{
|
|
debug("Check for enable");
|
|
var domain = parseURI.parseUri(uri);
|
|
domain = domain["host"];
|
|
debug("Is gpass enabled for " + domain + " ?");
|
|
|
|
return get_preference("disable-" + domain);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
function save_gpass_enable_config(uri, enable)
|
|
{
|
|
var domain = parseURI.parseUri(uri);
|
|
domain = domain["host"];
|
|
|
|
key = "disable-" + domain;
|
|
if (enable)
|
|
{
|
|
debug("Enable gpass for " + domain);
|
|
delete_preference(key);
|
|
}
|
|
else
|
|
{
|
|
debug("Disable gpass for " + domain);
|
|
set_preference(key, true, null);
|
|
}
|
|
}
|
|
|
|
function _query_tabs_is_gpass_enabled(tabs, sendResponse)
|
|
{
|
|
if (tabs.length)
|
|
{
|
|
is_gpass_enabled(tabs[0].url).then(
|
|
function (key_present) {
|
|
enabled = (key_present == null);
|
|
update_gpass_icon((enabled)?GPASS_ICON.NORMAL:GPASS_ICON.DISABLED, tabs[0].id);
|
|
sendResponse({"enabled":enabled});
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
debug("No cur tab");
|
|
sendResponse({"enabled":true});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function _query_tabs_update_icon(tabs, iconId)
|
|
{
|
|
if (tabs.length)
|
|
{
|
|
update_gpass_icon(iconId, tabs[0].id);
|
|
}
|
|
}
|
|
|
|
function update_enable(enabled, tab, saveConfig)
|
|
{
|
|
if (enabled)
|
|
{
|
|
parameters = {type:"blockForms"};
|
|
saveConfig = true;// Force save when enable website
|
|
debug("Now enabled");
|
|
}
|
|
else
|
|
{
|
|
parameters = {type:"unblockForms"};
|
|
debug("Now disabled");
|
|
}
|
|
|
|
if (saveConfig)
|
|
save_gpass_enable_config(tab.url, enabled);
|
|
update_gpass_icon((enabled)?GPASS_ICON.NORMAL:GPASS_ICON.DISABLED, tab.id);
|
|
browser.tabs.sendMessage(tab.id, parameters);
|
|
}
|
|
|
|
function gpass_switch_enable(tab)
|
|
{
|
|
debug("Switch enable");
|
|
is_gpass_enabled(tab.url).then(
|
|
function (key_present)
|
|
{
|
|
enabled = (key_present == null);
|
|
// Do switch
|
|
enabled = !enabled;
|
|
update_enable(enabled, tab, true);
|
|
});
|
|
}
|
|
|
|
function extension_load()
|
|
{
|
|
browser.runtime.onMessage.addListener(
|
|
function(request, sender, sendResponse) {
|
|
if (request.type == "password")
|
|
{
|
|
var domain = parseURI.parseUri(request.domain);
|
|
domain = domain["host"];
|
|
var wdomain = wildcard_domain(domain);
|
|
|
|
ask_server(request.logins, domain,
|
|
wdomain, request.mkey,
|
|
sendResponse, request.options);
|
|
|
|
return true;
|
|
}
|
|
else if (request.type == "notification")
|
|
{
|
|
_notification(request.options.message, request.options.data);
|
|
}
|
|
else if (request.type == "getServerAddress")
|
|
{
|
|
get_preference("account_url").then(
|
|
function (address) {
|
|
sendResponse({"value" : address});
|
|
});
|
|
return true;
|
|
}
|
|
else if (request.type == "is_gpass_enabled")
|
|
{
|
|
browser.tabs.query({active:true, currentWindow:true},
|
|
function cb(tabs) {
|
|
_query_tabs_is_gpass_enabled(tabs, sendResponse);
|
|
});
|
|
return true;
|
|
}
|
|
else if (request.type == "update_icon")
|
|
{
|
|
debug("update_icon");
|
|
browser.tabs.query({active:true, currentWindow:true},
|
|
function cb(tabs) {
|
|
_query_tabs_update_icon(tabs, request.icon_id);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
debug("Unknown message " + request.type);
|
|
}
|
|
}
|
|
);
|
|
|
|
if (!browser.menus && browser.contextMenus)
|
|
browser.menus = browser.contextMenus;
|
|
|
|
/* Settings */
|
|
browser.menus.create({
|
|
id: 'settings',
|
|
title: 'gPass Settings',
|
|
contexts: ['browser_action']
|
|
});
|
|
|
|
/* Not supported by Chrome */
|
|
if (browser.menus.onShown)
|
|
title = 'Disable gPass for this website';
|
|
else
|
|
title = 'Disable or enable gPass for this website';
|
|
|
|
/* Enable/disable */
|
|
browser.menus.create({
|
|
id: 'switch_enable',
|
|
title: title,
|
|
contexts: ['browser_action']
|
|
});
|
|
|
|
/* Not supported by Chrome */
|
|
if (browser.menus.onShown)
|
|
title = 'Disable gPass for ALL websites';
|
|
else
|
|
title = 'Disable or enable gPass for ALL websites';
|
|
|
|
/* Always enable/disable */
|
|
browser.menus.create({
|
|
id: 'always_disable',
|
|
title: title,
|
|
contexts: ['browser_action']
|
|
});
|
|
|
|
browser.menus.onClicked.addListener(
|
|
function(info, tab) {
|
|
switch (info.menuItemId) {
|
|
case 'settings':
|
|
browser.runtime.openOptionsPage();
|
|
break;
|
|
|
|
case 'always_disable':
|
|
get_preference('always_disabled').then(
|
|
function (always_disabled) {
|
|
debug('Change always disable');
|
|
debug(always_disabled);
|
|
always_disabled = !always_disabled;
|
|
|
|
set_preference('always_disabled', always_disabled,
|
|
function(error)
|
|
{
|
|
browser.tabs.query({},
|
|
function cb(tabs) {
|
|
for (i=0; i<tabs.length; i++)
|
|
update_enable(!always_disabled, tabs[i], false);
|
|
});
|
|
}
|
|
);
|
|
}
|
|
);
|
|
break;
|
|
case 'switch_enable':
|
|
gpass_switch_enable(tab);
|
|
break;
|
|
}
|
|
}
|
|
);
|
|
|
|
if (browser.menus.onShown)
|
|
{
|
|
browser.menus.onShown.addListener(
|
|
function(info, tab) {
|
|
is_gpass_enabled(tab.url).then(
|
|
function (key_present) {
|
|
enabled = (key_present == null);
|
|
if (enabled)
|
|
title = 'Disable gPass for this website';
|
|
else
|
|
title = 'Enable gPass for this website';
|
|
browser.menus.update("switch_enable",
|
|
{
|
|
"title":title
|
|
}
|
|
);
|
|
browser.menus.refresh();
|
|
}
|
|
);
|
|
}
|
|
);
|
|
|
|
browser.menus.onShown.addListener(
|
|
function(info, tab) {
|
|
get_preference('always_disabled').then(
|
|
function (always_disabled) {
|
|
if (always_disabled)
|
|
title = 'Enable gPass for ALL websites';
|
|
else
|
|
title = 'Disable gPass for ALL websites';
|
|
browser.menus.update("always_disable",
|
|
{
|
|
"title":title
|
|
}
|
|
);
|
|
browser.menus.refresh();
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
async function self_test()
|
|
{
|
|
mkey = crypto_pbkdf2("password", "salt", 4096);
|
|
res = await encrypt_ecb(mkey, "DDDDDDDDDDDDDDDD");
|
|
|
|
reference = new Uint8Array([0xc4, 0x76, 0x01, 0x07, 0xa1, 0xc0, 0x2f, 0x22, 0xee, 0xbe, 0x60,
|
|
0xff, 0x65, 0x33, 0x5b, 0x9e]);
|
|
if (res != ab2str(reference))
|
|
{
|
|
console.log("Self test ERROR !");
|
|
}
|
|
else
|
|
console.log("Self test OK !");
|
|
}
|
|
|
|
//self_test();
|
|
|
|
extension_load();
|