diff --git a/ChangeLog b/ChangeLog
index 60d0b6d..683aec7 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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 :**
Server
diff --git a/chrome_addon/background.js b/chrome_addon/background.js
index 1b044b7..86bb22d 100644
--- a/chrome_addon/background.js
+++ b/chrome_addon/background.js
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2013-2020 Grégory Soutadé
+ Copyright (C) 2013-2022 Grégory Soutadé
This file is part of gPass.
@@ -17,6 +17,11 @@
along with gPass. If not, see .
*/
+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 protocol_version = 4;
var account_url = null;
@@ -26,16 +31,14 @@ function _notification(message, data)
if (message !== data)
message += data;
- options = {
+ var options = {
type: "basic",
title : "gPass",
message : message,
- iconUrl:browser.extension.getURL("icons/gpass_icon_64.png")
+ iconUrl:"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)
@@ -44,34 +47,36 @@ async function generate_request(domain, login, mkey, iv)
debug("will encrypt " + v);
while ((v.length % 16))
v += "\0";
- hash = await digest(v);
+ var hash = await digest(crypto, v);
v += hash.slice(8, 24);
- enc = encrypt_cbc(mkey, iv, v);
+ var 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 account_url = await get_preference("account_url");
- var salt = parseURI.parseUri(account_url);
+ var salt = parseUri(account_url);
salt = salt["host"] + salt["path"];
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);
- mkey = crypto_pbkdf2(mkey, salt, pbkdf2_level);
+ var mkey = crypto_pbkdf2(crypto, mkey, salt, pbkdf2_level);
debug("global_iv " + a2hex(global_iv));
- keys = "";
+ var keys = "";
+ var key_index;
+ var a;
for(key_index=0, a=0; a {
+ debug(e);
+ _notification("Network error : check your server address", "");
+ });
- var r = this.responseText.split("\n");
- debug("resp " + r);
+ if (response === undefined)
+ {
+ ret = SERVER.ERROR;
+ sendResponse({"value": ret, options:options});
+ return;
+ }
+
+ const responseText = await response.text();
- for(var a=0; a")
{
- debug("Analyse " + r[a]);
+ _notification("Error : It seems that it's not a gPass server",
+ responseText);
+ ret = SERVER.FAILED;
+ break;
+ }
- params = r[a].split("=");
- if (params.length != 2 && params[0] != "")
+ 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);
+ responseText);
ret = SERVER.FAILED;
break;
}
- switch(params[0])
- {
- case "protocol":
- debug("protocol : " + params[1]);
+ var server_protocol_version = params[1].match(/\d+/)[0];
- 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",
- this.responseText);
- ret = SERVER.FAILED;
+ case 2:
+ server_pbkdf2_level = 1000;
+ break;
+ case 3:
+ // Version 3 : nothing special to do
+ case 4:
+ // Version 4 : nothing special to do
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 "":
- 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");
+ 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 "":
+ break;
+ default:
+ debug("Unknown command " + params[0]);
+ _notification("Error : It seems that it's not a gPass server",
+ responseText);
ret = SERVER.FAILED;
-
- _notification("No password found in database", "")
+ break;
}
+ }
+ 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});
- }, 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);
+ }
+ else
+ {
+ debug("No password found");
- return true;
+ ret = SERVER.FAILED;
+
+ _notification("No password found in database", "")
+
+ sendResponse({"value": ret, "options":options});
+ }
+
+ return;
}
function update_gpass_icon(iconId, tabId)
{
debug("update_gpass_icon");
- icon_infos = {"tabId":tabId};
- icon_name = "";
+ var icon_infos = {"tabId":tabId};
+ var icon_name = "";
switch (iconId)
{
@@ -237,6 +246,8 @@ function update_gpass_icon(iconId, tabId)
debug(icon_name);
+ var icon_infos = {};
+
icon_infos["path"] = {
16:"icons/gpass" + icon_name + "_icon_16.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",
};
- 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)
@@ -262,7 +276,7 @@ async function is_gpass_enabled(uri)
else
{
debug("Check for enable");
- var domain = parseURI.parseUri(uri);
+ var domain = parseUri(uri);
domain = domain["host"];
debug("Is gpass enabled for " + domain + " ?");
@@ -274,10 +288,10 @@ async function is_gpass_enabled(uri)
function save_gpass_enable_config(uri, enable)
{
- var domain = parseURI.parseUri(uri);
+ var domain = parseUri(uri);
domain = domain["host"];
- key = "disable-" + domain;
+ var key = "disable-" + domain;
if (enable)
{
debug("Enable gpass for " + domain);
@@ -296,7 +310,7 @@ function _query_tabs_is_gpass_enabled(tabs, sendResponse)
{
is_gpass_enabled(tabs[0].url).then(
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);
sendResponse({"enabled":enabled});
}
@@ -321,6 +335,8 @@ function _query_tabs_update_icon(tabs, iconId)
function update_enable(enabled, tab, saveConfig)
{
+ var parameters;
+
if (enabled)
{
parameters = {type:"blockForms"};
@@ -345,20 +361,54 @@ function gpass_switch_enable(tab)
is_gpass_enabled(tab.url).then(
function (key_present)
{
- enabled = (key_present == null);
+ var enabled = (key_present == null);
// Do switch
enabled = !enabled;
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()
{
browser.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.type == "password")
{
- var domain = parseURI.parseUri(request.domain);
+ var domain = parseUri(request.domain);
domain = domain["host"];
var wdomain = wildcard_domain(domain);
@@ -390,19 +440,19 @@ function extension_load()
}
else if (request.type == "is_gpass_enabled")
{
- browser.tabs.query({active:true, currentWindow:true},
- function cb(tabs) {
- _query_tabs_is_gpass_enabled(tabs, sendResponse);
- });
+ browser.tabs.query({active:true, currentWindow:true}).then( (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);
- });
+ browser.tabs.query({active:true, currentWindow:true}).then( (tabs) =>
+ {
+ _query_tabs_update_icon(tabs, request.icon_id);
+ });
}
else
{
@@ -411,116 +461,88 @@ function extension_load()
}
);
+ /* Chrome */
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.runtime.onInstalled.addListener(() => {
+ createMenus(browser)
});
browser.menus.onClicked.addListener(
- function(info, tab) {
- switch (info.menuItemId) {
- case 'settings':
- browser.runtime.openOptionsPage();
- break;
+ function(info, tab) {
+ switch (info.menuItemId) {
- case 'always_disable':
- get_preference('always_disabled').then(
- function (always_disabled) {
+ case 'always_disable':
+ get_preference('always_disabled').then(
+ function (always_disabled) {
debug('Change always disable');
debug(always_disabled);
- always_disabled = !always_disabled;
+ always_disabled = !always_disabled;
set_preference('always_disabled', always_disabled,
function(error)
{
- browser.tabs.query({},
- function cb(tabs) {
- for (i=0; i {
+ for (var i=0; i {
@@ -50,9 +50,9 @@ function get_preference(key)
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) {
if (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);
}
-function send_tab_message(tab_id, parameters, callback)
+export function send_tab_message(tab_id, parameters, callback)
{
chrome.tabs.sendMessage(tab_id, parameters, {}, callback);
}
+
diff --git a/chrome_addon/lib/main.js b/chrome_addon/lib/main.js
index 29e8143..bac768f 100644
--- a/chrome_addon/lib/main.js
+++ b/chrome_addon/lib/main.js
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2013-2020 Grégory Soutadé
+ Copyright (C) 2013-2022 Grégory Soutadé
This file is part of gPass.
@@ -17,30 +17,27 @@
along with gPass. If not, see .
*/
-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)
- message += data;
-
- 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);
- }
- );
+ if (DEBUG)
+ console.log(s);
}
+function notify(text, data)
+{
+ browser.runtime.sendMessage({type: "notification", options:{"message":text, "data":data}});
+}
+
+
+var gpass_enabled = true;
+
function _add_name(logins, name)
{
for(var i=0; i &&
type_filters.push("text");
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)
sendResponse(logins[0]);
@@ -347,12 +344,12 @@ browser.runtime.onMessage.addListener(
var response = "";
if (managed_forms.length == 1)
{
- fields = managed_forms[0].getElementsByTagName("input");
- password_field = null;
+ var fields = managed_forms[0].getElementsByTagName("input");
+ var password_field = null;
- for (a=0; a 16)
{
@@ -186,13 +186,14 @@ async function encrypt_ecb(mkey, data)
return result;
}
-async function decrypt_ecb(mkey, data)
+export async function decrypt_ecb(mkey, data)
{
var result = "";
-
+ var res;
+
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)
{
@@ -207,7 +208,7 @@ async function decrypt_ecb(mkey, data)
return result;
}
-async function encrypt_cbc(mkey, iv, data)
+export async function encrypt_cbc(mkey, iv, data)
{
debug("Encrypt CBC " + data);
@@ -217,7 +218,7 @@ async function encrypt_cbc(mkey, iv, data)
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);
@@ -227,22 +228,22 @@ async function decrypt_cbc(mkey, iv, data)
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 ab2str(hash);
});
}
-function wildcard_domain(domain)
+export function wildcard_domain(domain)
{
var parts = domain.split(".");
// Standard root domain (zzz.xxx.com) or more
if (parts.length > 2)
{
- res = "*.";
- for (i=1; i
// MIT License
-parseURI = {
-
- parseUri : function (str) {
- var o = {
+export function parseUri (str) {
+ var o = {
strictMode: false,
key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
q: {
- name: "queryKey",
- parser: /(?:^|&)([^&=]*)=?([^&]*)/g
+ name: "queryKey",
+ parser: /(?:^|&)([^&=]*)=?([^&]*)/g
},
parser: {
- strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
- loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
+ strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
+ loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
}},
- m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
- uri = {},
- i = 14;
+ m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
+ uri = {},
+ i = 14;
- while (i--) uri[o.key[i]] = m[i] || "";
+ 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;
- });
+ 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;
+}
- return uri;
- }
-};
diff --git a/chrome_addon/manifest.json b/chrome_addon/manifest.json
index e8a871f..c72b613 100644
--- a/chrome_addon/manifest.json
+++ b/chrome_addon/manifest.json
@@ -1,9 +1,9 @@
{
- "manifest_version": 2,
+ "manifest_version": 3,
"name": "gPass",
"short_name": "gPass",
- "version": "1.1",
+ "version": "1.2",
"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"},
"author" : "Grégory Soutadé",
@@ -12,30 +12,32 @@
"content_scripts": [
{
"matches": [""],
- "js": ["lib/misc.js", "lib/main.js"],
+ "js": ["lib/main.js"],
"run_at" : "document_idle",
"all_frames" : true
}
],
"background": {
- "persistent": true,
- "scripts": ["lib/parseuri.js", "lib/misc.js", "compat.js", "background.js"]
+ "service_worker": "background.js",
+ "type": "module"
},
"options_page": "options.html",
- "browser_action": {
+ "action": {
"default_icon": {"32":"icons/gpass_icon_32.png"},
"default_title": "Get your password",
"default_popup": "popup/popup.html"
},
+ "host_permissions": [
+ ""
+ ],
+
"permissions": [
- "",
"activeTab",
"notifications",
- "tabs",
"storage",
"clipboardWrite",
"contextMenus"
diff --git a/chrome_addon/popup/popup.html b/chrome_addon/popup/popup.html
index a2e4dbd..6c2785c 100644
--- a/chrome_addon/popup/popup.html
+++ b/chrome_addon/popup/popup.html
@@ -9,8 +9,6 @@
Copy password into clipboard
Your server
-
-
-
+