Update manifest to v3 (Chrome only)

This commit is contained in:
Grégory Soutadé 2022-09-26 20:30:40 +02:00
parent 2cb26d6d40
commit 129335dc2d
9 changed files with 403 additions and 363 deletions

View File

@ -1,3 +1,18 @@
**v1.2 :**
Server
* Rework UI
* Display PHP parsed URL in new URL case, not raw URL
* Add a filter for unciphered passwords (supports regular expressions)
* Add a button to copy unciphered password into clipboard
* Don't clear URL and login when adding a new password
* Change alert() by button name update for "Update" and "Copy clipboard" functions
Addon
* Update manifest to v3 (Chrome only)
CLI
**v1.1 :** **v1.1 :**
Server Server

View File

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2013-2020 Grégory Soutadé Copyright (C) 2013-2022 Grégory Soutadé
This file is part of gPass. This file is part of gPass.
@ -17,6 +17,11 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>. along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {parseUri} from "./lib/parseuri.js";
import {SERVER, GPASS_ICON, wildcard_domain, simple_pbkdf2, crypto_pbkdf2,
encrypt_ecb, encrypt_cbc, decrypt_cbc, digest, a2hex, hex2a, debug} from "./lib/misc.js";
import {get_preference, set_preference, delete_preference} from "./compat.js";
var browser = browser || chrome; var browser = browser || chrome;
var protocol_version = 4; var protocol_version = 4;
var account_url = null; var account_url = null;
@ -26,16 +31,14 @@ function _notification(message, data)
if (message !== data) if (message !== data)
message += data; message += data;
options = { var options = {
type: "basic", type: "basic",
title : "gPass", title : "gPass",
message : message, message : message,
iconUrl:browser.extension.getURL("icons/gpass_icon_64.png") iconUrl:"icons/gpass_icon_64.png"
}; };
browser.notifications.create("gPass", options, function(){}); browser.notifications.create("gPass", options, function(){});
window.setTimeout(function() {browser.notifications.clear("gPass", function(){})}, 2000);
} }
async function generate_request(domain, login, mkey, iv) async function generate_request(domain, login, mkey, iv)
@ -44,34 +47,36 @@ async function generate_request(domain, login, mkey, iv)
debug("will encrypt " + v); debug("will encrypt " + v);
while ((v.length % 16)) while ((v.length % 16))
v += "\0"; v += "\0";
hash = await digest(v); var hash = await digest(crypto, v);
v += hash.slice(8, 24); v += hash.slice(8, 24);
enc = encrypt_cbc(mkey, iv, v); var enc = encrypt_cbc(mkey, iv, v);
return enc; return enc;
} }
async function ask_server(logins, domain, wdomain, mkey, sendResponse, options) async function ask_server(logins, domain, wdomain, mkey, sendResponse, options)
{ {
account_url = await get_preference("account_url"); var account_url = await get_preference("account_url");
var salt = parseURI.parseUri(account_url); var salt = parseUri(account_url);
salt = salt["host"] + salt["path"]; salt = salt["host"] + salt["path"];
debug("salt " + salt); debug("salt " + salt);
pbkdf2_level = await get_preference("pbkdf2_level"); var pbkdf2_level = await get_preference("pbkdf2_level");
global_iv = await simple_pbkdf2(salt, mkey, pbkdf2_level); var global_iv = await simple_pbkdf2(crypto, salt, mkey, pbkdf2_level);
global_iv = global_iv.slice(0, 16); global_iv = global_iv.slice(0, 16);
mkey = crypto_pbkdf2(mkey, salt, pbkdf2_level); var mkey = crypto_pbkdf2(crypto, mkey, salt, pbkdf2_level);
debug("global_iv " + a2hex(global_iv)); debug("global_iv " + a2hex(global_iv));
keys = ""; var keys = "";
var key_index;
var a;
for(key_index=0, a=0; a<logins.length; a++, key_index++) for(key_index=0, a=0; a<logins.length; a++, key_index++)
{ {
enc = await generate_request(domain, logins[a], mkey, global_iv); var enc = await generate_request(domain, logins[a], mkey, global_iv);
keys += (keys.length != 0) ? "&" : ""; keys += (keys.length != 0) ? "&" : "";
keys += "k" + key_index + "=" + a2hex(enc); keys += "k" + key_index + "=" + a2hex(enc);
@ -85,143 +90,147 @@ async function ask_server(logins, domain, wdomain, mkey, sendResponse, options)
debug("Keys " + keys); debug("Keys " + keys);
var gPassRequest = new XMLHttpRequest();
var ret = SERVER.OK; var ret = SERVER.OK;
// gPassRequest.addEventListener("progress", function(evt) { ; }, false); debug("connect to " + account_url);
gPassRequest.addEventListener("load", async function(evt) { const headers = {'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'};
var ciphered_password = ""; const request = new Request(account_url, {method: 'POST', headers: headers, body: keys});
var clear_password = "";
var server_pbkdf2_level = 0; const response = await fetch(request).catch((e) => {
var server_version = 0; debug(e);
var matched_key = 0; _notification("Network error : check your server address", "");
});
var r = this.responseText.split("\n"); if (response === undefined)
debug("resp " + r); {
ret = SERVER.ERROR;
sendResponse({"value": ret, options:options});
return;
}
const responseText = await response.text();
for(var a=0; a<r.length; a++) var ciphered_password = "";
var server_pbkdf2_level = 0;
var server_version = 0;
var matched_key = 0;
var r = responseText.split("\n");
debug("resp " + r);
for(var a=0; a<r.length; a++)
{
debug("Analyse " + r[a]);
var params = r[a].split("=");
if (params.length != 2 && params[0] != "<end>")
{ {
debug("Analyse " + r[a]); _notification("Error : It seems that it's not a gPass server",
responseText);
ret = SERVER.FAILED;
break;
}
params = r[a].split("="); switch(params[0])
if (params.length != 2 && params[0] != "<end>") {
case "protocol":
debug("protocol : " + params[1]);
if (params[1].indexOf("gpass-") != 0)
{ {
_notification("Error : It seems that it's not a gPass server", _notification("Error : It seems that it's not a gPass server",
this.responseText); responseText);
ret = SERVER.FAILED; ret = SERVER.FAILED;
break; break;
} }
switch(params[0]) var server_protocol_version = params[1].match(/\d+/)[0];
{
case "protocol":
debug("protocol : " + params[1]);
if (params[1].indexOf("gpass-") != 0) if (server_protocol_version > protocol_version)
{
_notification("Protocol version not supported, please upgrade your addon", "");
ret = SERVER.FAILED;
}
else
{
switch (server_protocol_version)
{ {
_notification("Error : It seems that it's not a gPass server", case 2:
this.responseText); server_pbkdf2_level = 1000;
ret = SERVER.FAILED; break;
case 3:
// Version 3 : nothing special to do
case 4:
// Version 4 : nothing special to do
break; 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;
} }
} break;
case "matched_key":
if (ret != SERVER.OK) matched_key = params[1];
{ case "pass":
sendResponse({"value": ret, options:options}); ciphered_password = params[1];
return; break;
} case "pkdbf2_level":
case "pbkdf2_level":
if (ciphered_password != "") server_pbkdf2_level = parseInt(params[1].match(/\d+/)[0], 10);
{ if (server_pbkdf2_level != NaN &&
debug("Ciphered password : " + ciphered_password); server_pbkdf2_level != pbkdf2_level &&
clear_password = await decrypt_cbc(mkey, global_iv, hex2a(ciphered_password)); server_pbkdf2_level >= 1000) // Minimum level for PBKDF2 !
clear_password = clear_password.replace(/\0*$/, ""); {
clear_password = clear_password.substr(3, clear_password.length); debug("New pbkdf2 level " + server_pbkdf2_level);
debug("Clear password " + clear_password); pbkdf2_level = server_pbkdf2_level;
} set_preference("pbkdf2_level", pbkdf2_level, null);
else ret = SERVER.RESTART_REQUEST;
{ }
debug("No password found"); break;
case "<end>":
break;
default:
debug("Unknown command " + params[0]);
_notification("Error : It seems that it's not a gPass server",
responseText);
ret = SERVER.FAILED; ret = SERVER.FAILED;
break;
_notification("No password found in database", "")
} }
}
if (ret != SERVER.OK)
{
sendResponse({"value": ret, options:options});
return;
}
if (ciphered_password != "")
{
debug("Ciphered password : " + ciphered_password);
var 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);
sendResponse({"value": ret, "password":clear_password, "options":options}); sendResponse({"value": ret, "password":clear_password, "options":options});
}, false); }
gPassRequest.addEventListener("error", function(evt) { else
debug("error"); {
ret = false; debug("No password found");
_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; ret = SERVER.FAILED;
_notification("No password found in database", "")
sendResponse({"value": ret, "options":options});
}
return;
} }
function update_gpass_icon(iconId, tabId) function update_gpass_icon(iconId, tabId)
{ {
debug("update_gpass_icon"); debug("update_gpass_icon");
icon_infos = {"tabId":tabId}; var icon_infos = {"tabId":tabId};
icon_name = ""; var icon_name = "";
switch (iconId) switch (iconId)
{ {
@ -237,6 +246,8 @@ function update_gpass_icon(iconId, tabId)
debug(icon_name); debug(icon_name);
var icon_infos = {};
icon_infos["path"] = { icon_infos["path"] = {
16:"icons/gpass" + icon_name + "_icon_16.png", 16:"icons/gpass" + icon_name + "_icon_16.png",
32:"icons/gpass" + icon_name + "_icon_32.png", 32:"icons/gpass" + icon_name + "_icon_32.png",
@ -244,7 +255,10 @@ function update_gpass_icon(iconId, tabId)
128:"icons/gpass" + icon_name + "_icon_128.png", 128:"icons/gpass" + icon_name + "_icon_128.png",
}; };
browser.browserAction.setIcon(icon_infos); if (browser.browserAction)
browser.browserAction.setIcon(icon_infos);
else
browser.action.setIcon(icon_infos);
} }
async function is_gpass_enabled(uri) async function is_gpass_enabled(uri)
@ -262,7 +276,7 @@ async function is_gpass_enabled(uri)
else else
{ {
debug("Check for enable"); debug("Check for enable");
var domain = parseURI.parseUri(uri); var domain = parseUri(uri);
domain = domain["host"]; domain = domain["host"];
debug("Is gpass enabled for " + domain + " ?"); debug("Is gpass enabled for " + domain + " ?");
@ -274,10 +288,10 @@ async function is_gpass_enabled(uri)
function save_gpass_enable_config(uri, enable) function save_gpass_enable_config(uri, enable)
{ {
var domain = parseURI.parseUri(uri); var domain = parseUri(uri);
domain = domain["host"]; domain = domain["host"];
key = "disable-" + domain; var key = "disable-" + domain;
if (enable) if (enable)
{ {
debug("Enable gpass for " + domain); debug("Enable gpass for " + domain);
@ -296,7 +310,7 @@ function _query_tabs_is_gpass_enabled(tabs, sendResponse)
{ {
is_gpass_enabled(tabs[0].url).then( is_gpass_enabled(tabs[0].url).then(
function (key_present) { function (key_present) {
enabled = (key_present == null); var enabled = (key_present == null);
update_gpass_icon((enabled)?GPASS_ICON.NORMAL:GPASS_ICON.DISABLED, tabs[0].id); update_gpass_icon((enabled)?GPASS_ICON.NORMAL:GPASS_ICON.DISABLED, tabs[0].id);
sendResponse({"enabled":enabled}); sendResponse({"enabled":enabled});
} }
@ -321,6 +335,8 @@ function _query_tabs_update_icon(tabs, iconId)
function update_enable(enabled, tab, saveConfig) function update_enable(enabled, tab, saveConfig)
{ {
var parameters;
if (enabled) if (enabled)
{ {
parameters = {type:"blockForms"}; parameters = {type:"blockForms"};
@ -345,20 +361,54 @@ function gpass_switch_enable(tab)
is_gpass_enabled(tab.url).then( is_gpass_enabled(tab.url).then(
function (key_present) function (key_present)
{ {
enabled = (key_present == null); var enabled = (key_present == null);
// Do switch // Do switch
enabled = !enabled; enabled = !enabled;
update_enable(enabled, tab, true); update_enable(enabled, tab, true);
}); });
} }
function createMenus(browser)
{
var title;
debug("Create menus");
/* 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: ['action'],
}, () => {console.log(chrome.runtime.lastError);});
/* 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: ['action']
});
}
function extension_load() function extension_load()
{ {
browser.runtime.onMessage.addListener( browser.runtime.onMessage.addListener(
function(request, sender, sendResponse) { function(request, sender, sendResponse) {
if (request.type == "password") if (request.type == "password")
{ {
var domain = parseURI.parseUri(request.domain); var domain = parseUri(request.domain);
domain = domain["host"]; domain = domain["host"];
var wdomain = wildcard_domain(domain); var wdomain = wildcard_domain(domain);
@ -390,19 +440,19 @@ function extension_load()
} }
else if (request.type == "is_gpass_enabled") else if (request.type == "is_gpass_enabled")
{ {
browser.tabs.query({active:true, currentWindow:true}, browser.tabs.query({active:true, currentWindow:true}).then( (tabs) =>
function cb(tabs) { {
_query_tabs_is_gpass_enabled(tabs, sendResponse); _query_tabs_is_gpass_enabled(tabs, sendResponse);
}); });
return true; return true;
} }
else if (request.type == "update_icon") else if (request.type == "update_icon")
{ {
debug("update_icon"); debug("update_icon");
browser.tabs.query({active:true, currentWindow:true}, browser.tabs.query({active:true, currentWindow:true}).then( (tabs) =>
function cb(tabs) { {
_query_tabs_update_icon(tabs, request.icon_id); _query_tabs_update_icon(tabs, request.icon_id);
}); });
} }
else else
{ {
@ -411,116 +461,88 @@ function extension_load()
} }
); );
/* Chrome */
if (!browser.menus && browser.contextMenus) if (!browser.menus && browser.contextMenus)
{
browser.menus = browser.contextMenus; browser.menus = browser.contextMenus;
}
/* Settings */ browser.runtime.onInstalled.addListener(() => {
browser.menus.create({ createMenus(browser)
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( browser.menus.onClicked.addListener(
function(info, tab) { function(info, tab) {
switch (info.menuItemId) { switch (info.menuItemId) {
case 'settings':
browser.runtime.openOptionsPage();
break;
case 'always_disable': case 'always_disable':
get_preference('always_disabled').then( get_preference('always_disabled').then(
function (always_disabled) { function (always_disabled) {
debug('Change always disable'); debug('Change always disable');
debug(always_disabled); debug(always_disabled);
always_disabled = !always_disabled; always_disabled = !always_disabled;
set_preference('always_disabled', always_disabled, set_preference('always_disabled', always_disabled,
function(error) function(error)
{ {
browser.tabs.query({}, browser.tabs.query({active:true, currentWindow:true},
function cb(tabs) { (tabs) => {
for (i=0; i<tabs.length; i++) for (var i=0; i<tabs.length; i++)
update_enable(!always_disabled, tabs[i], false); update_enable(!always_disabled, tabs[i], false);
}); });
} }
); );
} }
); );
break; break;
case 'switch_enable': case 'switch_enable':
gpass_switch_enable(tab); gpass_switch_enable(tab);
break; break;
} }
} }
); );
/* Firefox only */
if (browser.menus.onShown) if (browser.menus.onShown)
{ {
browser.menus.onShown.addListener( browser.menus.onShown.addListener(
function(info, tab) { function(info, tab) {
is_gpass_enabled(tab.url).then( is_gpass_enabled(tab.url).then(
function (key_present) { function (key_present) {
enabled = (key_present == null); enabled = (key_present == null);
if (enabled) if (enabled)
title = 'Disable gPass for this website'; title = 'Disable gPass for this website';
else else
title = 'Enable gPass for this website'; title = 'Enable gPass for this website';
browser.menus.update("switch_enable", browser.menus.update("switch_enable",
{ {
"title":title "title":title
} }
); );
browser.menus.refresh(); browser.menus.refresh();
} }
); );
} }
); );
browser.menus.onShown.addListener( browser.menus.onShown.addListener(
function(info, tab) { function(info, tab) {
get_preference('always_disabled').then( get_preference('always_disabled').then(
function (always_disabled) { function (always_disabled) {
if (always_disabled) if (always_disabled)
title = 'Enable gPass for ALL websites'; title = 'Enable gPass for ALL websites';
else else
title = 'Disable gPass for ALL websites'; title = 'Disable gPass for ALL websites';
browser.menus.update("always_disable", browser.menus.update("always_disable",
{ {
"title":title "title":title
} }
); );
browser.menus.refresh(); browser.menus.refresh();
} }
); );
} }
); );
} }
} }
@ -540,5 +562,4 @@ async function self_test()
} }
//self_test(); //self_test();
extension_load(); extension_load();

View File

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2013-2020 Grégory Soutadé Copyright (C) 2013-2022 Grégory Soutadé
This file is part of gPass. This file is part of gPass.
@ -23,7 +23,7 @@ var default_preferences = {"pbkdf2_level": 1000,
"popup_clipboard":false "popup_clipboard":false
}; };
function get_preference(key) export function get_preference(key)
{ {
// Inspired from https://github.com/akiomik/chrome-storage-promise/ // Inspired from https://github.com/akiomik/chrome-storage-promise/
var promise = new Promise((resolve, reject) => { var promise = new Promise((resolve, reject) => {
@ -50,9 +50,9 @@ function get_preference(key)
return promise; return promise;
} }
function set_preference(key, value, sendResponse) export function set_preference(key, value, sendResponse)
{ {
pref = {[key]:value}; var pref = {[key]:value};
chrome.storage.local.set(pref, function (result) { chrome.storage.local.set(pref, function (result) {
if (chrome.runtime.lastError) if (chrome.runtime.lastError)
alert(chrome.runtime.lastError); alert(chrome.runtime.lastError);
@ -61,12 +61,13 @@ function set_preference(key, value, sendResponse)
}); });
} }
function delete_preference(key) export function delete_preference(key)
{ {
chrome.storage.local.remove(key); chrome.storage.local.remove(key);
} }
function send_tab_message(tab_id, parameters, callback) export function send_tab_message(tab_id, parameters, callback)
{ {
chrome.tabs.sendMessage(tab_id, parameters, {}, callback); chrome.tabs.sendMessage(tab_id, parameters, {}, callback);
} }

View File

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2013-2020 Grégory Soutadé Copyright (C) 2013-2022 Grégory Soutadé
This file is part of gPass. This file is part of gPass.
@ -17,30 +17,27 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>. along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/ */
var gpass_enabled = true; /* from misc.js */
/* Can't directly add it, because it's now a module */
var DEBUG = false;
var browser = browser || chrome;
const SERVER = {OK : 0, FAILED : 1, RESTART_REQUEST : 2};
const GPASS_ICON = {NORMAL:0, DISABLED:1, ACTIVATED:2};
function _notification(message, data) function debug(s)
{ {
if (message !== data) if (DEBUG)
message += data; console.log(s);
options = {
type: "basic",
title : "gPass",
message : message,
iconUrl:browser.extension.getURL("icons/gpass_icon_64.png")
};
browser.notifications.create(options).then(
function created(notification_id)
{
window.setTimeout(function() {
browser.notifications.clear(notification_id);
}, 2000);
}
);
} }
function notify(text, data)
{
browser.runtime.sendMessage({type: "notification", options:{"message":text, "data":data}});
}
var gpass_enabled = true;
function _add_name(logins, name) function _add_name(logins, name)
{ {
for(var i=0; i<logins.length; i++) for(var i=0; i<logins.length; i++)
@ -77,7 +74,7 @@ function try_get_name(fields, type_filters, match)
{ {
if (field.hasAttribute("name") && field.value != "") if (field.hasAttribute("name") && field.value != "")
{ {
name = field.getAttribute("name"); var name = field.getAttribute("name");
// Subset of common user field // Subset of common user field
if (name == "user") user = field.value; if (name == "user") user = field.value;
else if (name == "usr") user = field.value; else if (name == "usr") user = field.value;
@ -130,7 +127,7 @@ function on_focus(e)
if (logins.length || all_logins.length) if (logins.length || all_logins.length)
{ {
parameters = { var parameters = {
type:"update_icon", type:"update_icon",
icon_id:GPASS_ICON.ACTIVATED, icon_id:GPASS_ICON.ACTIVATED,
}; };
@ -143,7 +140,7 @@ function on_blur(e)
if (!gpass_enabled) if (!gpass_enabled)
return; return;
parameters = { var parameters = {
type:"update_icon", type:"update_icon",
icon_id:GPASS_ICON.NORMAL, icon_id:GPASS_ICON.NORMAL,
}; };
@ -159,8 +156,8 @@ function on_sumbit(e)
debug("on_submit"); debug("on_submit");
logins = get_logins(form, true); var logins = get_logins(form, true);
all_logins = get_logins(form, false); var all_logins = get_logins(form, false);
if (!logins.length) if (!logins.length)
logins = all_logins; logins = all_logins;
@ -174,17 +171,17 @@ function on_sumbit(e)
if (field.getAttribute("type") == "password") if (field.getAttribute("type") == "password")
{ {
password = field.value; var password = field.value;
if (!password.startsWith("@@") && !password.startsWith("@_")) if (!password.startsWith("@@") && !password.startsWith("@_"))
continue; continue;
// Remove current value to limit master key stealing // Remove current value to limit master key stealing
field.value = ""; field.value = "";
password_computed = true; password_computed = true;
do_submit = !password.startsWith("@_"); var do_submit = !password.startsWith("@_");
mkey = password.substring(2); var mkey = password.substring(2);
parameters = { var parameters = {
type:"password", type:"password",
logins:logins, logins:logins,
domain:domain, domain:domain,
@ -195,18 +192,19 @@ function on_sumbit(e)
browser.runtime.sendMessage(parameters, {}, browser.runtime.sendMessage(parameters, {},
function (response) { function (response) {
debug(response); debug(response);
var field = fields[response.options.field_id];
switch(response.value) switch(response.value)
{ {
case SERVER.OK: case SERVER.OK:
var field = fields[response.options.field_id];
set_password(form, field, response.password, do_submit) set_password(form, field, response.password, do_submit)
notify("Password successfully replaced", ""); notify("Password successfully replaced", "");
break; break;
case SERVER.FAILED: case SERVER.FAILED:
if (logins.length != all_logins.length) if (logins.length != all_logins.length && all_logins.length != 0)
{ {
parameters[logins] = all_logins; debug("Try with all logins");
browser.runtime.sendMessage(parameters); parameters[logins] = all_logins;
browser.runtime.sendMessage(parameters);
} }
break; break;
case SERVER.RESTART_REQUEST: case SERVER.RESTART_REQUEST:
@ -214,7 +212,7 @@ function on_sumbit(e)
break; break;
} }
} }
); );
} }
} }
@ -222,10 +220,9 @@ function on_sumbit(e)
{ {
debug("No password computed"); debug("No password computed");
form.submit(); form.submit();
return true;
} }
return false; return true;
} }
function set_password(form, field, password, do_submit) function set_password(form, field, password, do_submit)
@ -236,7 +233,7 @@ function set_password(form, field, password, do_submit)
if (do_submit) if (do_submit)
{ {
// Propagate change // Propagate change
change_cb = field.onchange; var change_cb = field.onchange;
if (change_cb) if (change_cb)
change_cb(); change_cb();
// Try to type "enter" // Try to type "enter"
@ -272,7 +269,7 @@ function block_all_forms(doc, do_block)
{ {
if (do_block) if (do_block)
{ {
old_cb = form.onsubmit; var old_cb = form.onsubmit;
if (old_cb) if (old_cb)
form.removeEventListener("submit", old_cb); form.removeEventListener("submit", old_cb);
form.addEventListener("submit", on_sumbit); form.addEventListener("submit", on_sumbit);
@ -325,13 +322,13 @@ browser.runtime.onMessage.addListener(
debug("getUsername"); debug("getUsername");
if (managed_forms.length == 1) if (managed_forms.length == 1)
{ {
fields = managed_forms[0].getElementsByTagName("input"); var fields = managed_forms[0].getElementsByTagName("input");
type_filters = new Array(); var type_filters = new Array();
// Get all <input type="text"> && <input type="email"> // Get all <input type="text"> && <input type="email">
type_filters.push("text"); type_filters.push("text");
type_filters.push("email"); type_filters.push("email");
logins = try_get_name(fields, type_filters, true); var logins = try_get_name(fields, type_filters, true);
if (logins.length == 1) if (logins.length == 1)
sendResponse(logins[0]); sendResponse(logins[0]);
@ -347,12 +344,12 @@ browser.runtime.onMessage.addListener(
var response = ""; var response = "";
if (managed_forms.length == 1) if (managed_forms.length == 1)
{ {
fields = managed_forms[0].getElementsByTagName("input"); var fields = managed_forms[0].getElementsByTagName("input");
password_field = null; var password_field = null;
for (a=0; a<fields.length; a++) for (var a=0; a<fields.length; a++)
{ {
field = fields[a]; var field = fields[a];
if (field.getAttribute("type") == "password") if (field.getAttribute("type") == "password")
{ {
if (password_field == null) if (password_field == null)
@ -369,12 +366,11 @@ browser.runtime.onMessage.addListener(
if (password_field) if (password_field)
{ {
set_password(managed_forms[0], password_field, set_password(managed_forms[0], password_field,
request.password, request.submit); request.password, request.submit);
response = "ok"; response = "ok";
} }
} }
sendResponse(response); sendResponse(response);
return true;
} }
else if (request.type == "blockForms") else if (request.type == "blockForms")
{ {
@ -384,11 +380,13 @@ browser.runtime.onMessage.addListener(
{ {
unblock_all_forms(); unblock_all_forms();
} }
return true;
}); });
function document_loaded() function document_loaded()
{ {
parameters = { var parameters = {
"type": "is_gpass_enabled", "type": "is_gpass_enabled",
}; };

View File

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2013-2020 Grégory Soutadé Copyright (C) 2013-2022 Grégory Soutadé
This file is part of gPass. This file is part of gPass.
@ -19,13 +19,12 @@
var DEBUG = false; var DEBUG = false;
SERVER = {OK : 0, FAILED : 1, RESTART_REQUEST : 2}; export const SERVER = {OK : 0, FAILED : 1, RESTART_REQUEST : 2};
GPASS_ICON = {NORMAL:0, DISABLED:1, ACTIVATED:2}; export const GPASS_ICON = {NORMAL:0, DISABLED:1, ACTIVATED:2};
var browser = browser || chrome; export var browser = browser || chrome;
var crypto = crypto || window.crypto;
function notify(text, data) export function notify(text, data)
{ {
browser.runtime.sendMessage({type: "notification", options:{"message":text, "data":data}}); browser.runtime.sendMessage({type: "notification", options:{"message":text, "data":data}});
} }
@ -54,9 +53,9 @@ function str2ab(str) {
return bufView; return bufView;
} }
function crypto_pbkdf2(mkey, salt, level) export function crypto_pbkdf2(crypto, mkey, salt, level)
{ {
AESCBC = { var AESCBC = {
name: "AES-CBC", name: "AES-CBC",
length: 256, length: 256,
} }
@ -83,9 +82,9 @@ function crypto_pbkdf2(mkey, salt, level)
}); });
} }
function simple_pbkdf2(mkey, salt, level) export function simple_pbkdf2(crypto, mkey, salt, level)
{ {
AESCBC = { var AESCBC = {
name: "AES-CBC", name: "AES-CBC",
length: 256, length: 256,
} }
@ -122,7 +121,7 @@ function _encrypt(mkey, iv, data)
data = str2ab(data); data = str2ab(data);
promise = mkey.then(function(mkey){ var promise = mkey.then(function(mkey){
return crypto.subtle.encrypt({ return crypto.subtle.encrypt({
name: "AES-CBC", name: "AES-CBC",
iv: iv iv: iv
@ -142,15 +141,15 @@ async function _decrypt(mkey, iv, data)
while ((data.length % 16)) while ((data.length % 16))
data += "\0"; data += "\0";
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); var 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]); var 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)); pkcs7_padding = await _encrypt(mkey, nulliv, ab2str(pkcs7_padding));
data = str2ab(data + pkcs7_padding); data = str2ab(data + pkcs7_padding);
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
promise = mkey.then(function(mkey){ var promise = mkey.then(function(mkey){
return crypto.subtle.decrypt({ return crypto.subtle.decrypt({
name: "AES-CBC", name: "AES-CBC",
iv: iv iv: iv
@ -165,13 +164,14 @@ async function _decrypt(mkey, iv, data)
return promise; return promise;
} }
async function encrypt_ecb(mkey, data) export async function encrypt_ecb(mkey, data)
{ {
var result = ""; var result = "";
var res;
debug("Encrypt ECB " + data); debug("Encrypt ECB " + data);
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); var nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
while (data.length > 16) while (data.length > 16)
{ {
@ -186,13 +186,14 @@ async function encrypt_ecb(mkey, data)
return result; return result;
} }
async function decrypt_ecb(mkey, data) export async function decrypt_ecb(mkey, data)
{ {
var result = ""; var result = "";
var res;
debug("Decrypt ECB " + data); debug("Decrypt ECB " + data);
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); var nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
while (data.length > 16) while (data.length > 16)
{ {
@ -207,7 +208,7 @@ async function decrypt_ecb(mkey, data)
return result; return result;
} }
async function encrypt_cbc(mkey, iv, data) export async function encrypt_cbc(mkey, iv, data)
{ {
debug("Encrypt CBC " + data); debug("Encrypt CBC " + data);
@ -217,7 +218,7 @@ async function encrypt_cbc(mkey, iv, data)
return result.slice(0, result.length-16); return result.slice(0, result.length-16);
} }
async function decrypt_cbc(mkey, iv, data) export async function decrypt_cbc(mkey, iv, data)
{ {
debug("Decrypt CBC " + data); debug("Decrypt CBC " + data);
@ -227,22 +228,22 @@ async function decrypt_cbc(mkey, iv, data)
return result.slice(0, result.length-16); return result.slice(0, result.length-16);
} }
async function digest(data) export async function digest(crypto, data)
{ {
return crypto.subtle.digest("SHA-256", str2ab(data)).then(function (hash) { return crypto.subtle.digest("SHA-256", str2ab(data)).then(function (hash) {
return ab2str(hash); return ab2str(hash);
}); });
} }
function wildcard_domain(domain) export function wildcard_domain(domain)
{ {
var parts = domain.split("."); var parts = domain.split(".");
// Standard root domain (zzz.xxx.com) or more // Standard root domain (zzz.xxx.com) or more
if (parts.length > 2) if (parts.length > 2)
{ {
res = "*."; var res = "*.";
for (i=1; i<parts.length; i++) for (var i=1; i<parts.length; i++)
res += parts[i] + "."; res += parts[i] + ".";
return res.substr(0, res.length-1); return res.substr(0, res.length-1);
} }
@ -254,14 +255,14 @@ function wildcard_domain(domain)
} }
// http://stackoverflow.com/questions/3745666/how-to-convert-from-hex-to-ascii-in-javascript // http://stackoverflow.com/questions/3745666/how-to-convert-from-hex-to-ascii-in-javascript
function hex2a(hex) { export function hex2a(hex) {
var str = ''; var str = '';
for (var i = 0; i < hex.length; i += 2) for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str; return str;
} }
function a2hex(_str_) { export function a2hex(_str_) {
var hex = ''; var hex = '';
for (var i = 0; i < _str_.length; i++) for (var i = 0; i < _str_.length; i++)
{ {
@ -272,8 +273,9 @@ function a2hex(_str_) {
return hex; return hex;
} }
function debug(s) export function debug(s)
{ {
if (DEBUG) if (DEBUG)
console.log(s); console.log(s);
} }

View File

@ -2,31 +2,29 @@
// (c) Steven Levithan <stevenlevithan.com> // (c) Steven Levithan <stevenlevithan.com>
// MIT License // MIT License
parseURI = { export function parseUri (str) {
var o = {
parseUri : function (str) {
var o = {
strictMode: false, strictMode: false,
key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
q: { q: {
name: "queryKey", name: "queryKey",
parser: /(?:^|&)([^&=]*)=?([^&]*)/g parser: /(?:^|&)([^&=]*)=?([^&]*)/g
}, },
parser: { parser: {
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
}}, }},
m = o.parser[o.strictMode ? "strict" : "loose"].exec(str), m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
uri = {}, uri = {},
i = 14; i = 14;
while (i--) uri[o.key[i]] = m[i] || ""; while (i--) uri[o.key[i]] = m[i] || "";
uri[o.q.name] = {}; uri[o.q.name] = {};
uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) { uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
if ($1) uri[o.q.name][$1] = $2; if ($1) uri[o.q.name][$1] = $2;
}); });
return uri;
}
return uri;
}
};

View File

@ -1,9 +1,9 @@
{ {
"manifest_version": 2, "manifest_version": 3,
"name": "gPass", "name": "gPass",
"short_name": "gPass", "short_name": "gPass",
"version": "1.1", "version": "1.2",
"description": "gPass : global password manager", "description": "gPass : global password manager",
"icons" : {"16":"icons/gpass_icon_16.png", "32":"icons/gpass_icon_32.png", "64":"icons/gpass_icon_64.png", "128":"icons/gpass_icon_128.png"}, "icons" : {"16":"icons/gpass_icon_16.png", "32":"icons/gpass_icon_32.png", "64":"icons/gpass_icon_64.png", "128":"icons/gpass_icon_128.png"},
"author" : "Grégory Soutadé", "author" : "Grégory Soutadé",
@ -12,30 +12,32 @@
"content_scripts": [ "content_scripts": [
{ {
"matches": ["<all_urls>"], "matches": ["<all_urls>"],
"js": ["lib/misc.js", "lib/main.js"], "js": ["lib/main.js"],
"run_at" : "document_idle", "run_at" : "document_idle",
"all_frames" : true "all_frames" : true
} }
], ],
"background": { "background": {
"persistent": true, "service_worker": "background.js",
"scripts": ["lib/parseuri.js", "lib/misc.js", "compat.js", "background.js"] "type": "module"
}, },
"options_page": "options.html", "options_page": "options.html",
"browser_action": { "action": {
"default_icon": {"32":"icons/gpass_icon_32.png"}, "default_icon": {"32":"icons/gpass_icon_32.png"},
"default_title": "Get your password", "default_title": "Get your password",
"default_popup": "popup/popup.html" "default_popup": "popup/popup.html"
}, },
"host_permissions": [
"<all_urls>"
],
"permissions": [ "permissions": [
"<all_urls>",
"activeTab", "activeTab",
"notifications", "notifications",
"tabs",
"storage", "storage",
"clipboardWrite", "clipboardWrite",
"contextMenus" "contextMenus"

View File

@ -9,8 +9,6 @@
Copy password into clipboard <input id="clipboard" type="checkbox"/><br /> Copy password into clipboard <input id="clipboard" type="checkbox"/><br />
<input id="getButton" type="submit" value="Get"/> <a id="serverLink" href="">Your server</a> <input id="getButton" type="submit" value="Get"/> <a id="serverLink" href="">Your server</a>
</form> </form>
<script src="misc.js"></script> <script type="module" src="popup.js"></script>
<script src="compat.js"></script>
<script src="popup.js"></script>
</body> </body>
</html> </html>

View File

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2020 Grégory Soutadé Copyright (C) 2020-2022 Grégory Soutadé
This file is part of gPass. This file is part of gPass.
@ -17,10 +17,13 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>. along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {SERVER, browser, notify, debug} from "./misc.js";
import {get_preference, send_tab_message} from "./compat.js";
var username_filled = false; var username_filled = false;
function _server_response(response, tabs, do_submit, force_copy) function _server_response(response, tabs, do_submit, force_copy)
{ {
debug("Get Response"); debug("Get Response");
if (response.value == SERVER.OK) if (response.value == SERVER.OK)
{ {
@ -29,17 +32,17 @@ function _server_response(response, tabs, do_submit, force_copy)
{ {
navigator.clipboard.writeText(response.password).then(function() { navigator.clipboard.writeText(response.password).then(function() {
notify("Password pasted into clipboard", ""); notify("Password pasted into clipboard", "");
window.close(); window.setTimeout(function() {window.close();}, 2000);
}); });
return true; return true;
} }
/* Fill + optional copy */ /* Fill + optional copy */
parameters = { var parameters = {
"type":"setPassword", "type":"setPassword",
"password":response.password, "password":response.password,
"submit":do_submit "submit":do_submit,
}; };
send_tab_message(tabs[0].id, parameters, send_tab_message(tabs[0].id, parameters,
function(arg) function(arg)
@ -49,13 +52,13 @@ function _server_response(response, tabs, do_submit, force_copy)
{ {
navigator.clipboard.writeText(response.password).then(function() { navigator.clipboard.writeText(response.password).then(function() {
notify("Password pasted into clipboard", ""); notify("Password pasted into clipboard", "");
window.close(); window.setTimeout(function() {window.close();}, 2000);
}); });
} }
else else
{ {
notify("Password filled", ""); notify("Password filled", "");
window.close(); window.setTimeout(function() {window.close();}, 2000);
} }
} }
); );
@ -91,7 +94,7 @@ function _query_tabs_get_password(tabs)
var logins = new Array(); var logins = new Array();
logins.push(username); logins.push(username);
parameters = { var parameters = {
type:"password", type:"password",
logins:logins, logins:logins,
domain:domain, domain:domain,
@ -115,12 +118,12 @@ function get_password(evt)
evt.preventDefault(); evt.preventDefault();
browser.tabs.query({active:true, currentWindow:true}, _query_tabs_get_password); browser.tabs.query({active:true, currentWindow:true}).then(_query_tabs_get_password);
return false; return false;
} }
pform = document.getElementById("passwordForm"); var pform = document.getElementById("passwordForm");
if (pform != null) if (pform != null)
pform.onsubmit = get_password; pform.onsubmit = get_password;
@ -132,7 +135,7 @@ function _query_tabs_init(tabs)
if (tabs.length != 1) return; if (tabs.length != 1) return;
/* Fill username */ /* Fill username */
parameters = { var parameters = {
"type":"getUsername" "type":"getUsername"
}; };
@ -166,20 +169,22 @@ function _query_tabs_init(tabs)
}; };
browser.runtime.sendMessage(parameters, {}, browser.runtime.sendMessage(parameters, {},
function (response) function (response)
{ {
url = response.value; if (tabs[0].url === undefined) return true;
url = url.substring(0, url.lastIndexOf('/')); if (tabs[0].url.startsWith("chrome://newtab/")) tabs[0].url = "";
url += '?'; var url = response.value;
url += 'url=' + encodeURI(tabs[0].url.split("?")[0]); url = url.substring(0, url.lastIndexOf('/'));
url += '&user=' + document.getElementById("gPassUsername").value; url += '?';
link = document.getElementById("serverLink"); url += 'url=' + encodeURI(tabs[0].url.split("?")[0]);
link.href = url; url += '&user=' + document.getElementById("gPassUsername").value;
var link = document.getElementById("serverLink");
return true; link.href = url;
});
return true;
});
return true; return true;
} }
browser.tabs.query({active:true, currentWindow:true}, _query_tabs_init); browser.tabs.query({active:true, currentWindow:true}).then(_query_tabs_init);