19 Commits
v1.0 ... master

Author SHA1 Message Date
1f8c250897 Update README.md 2024-04-28 10:17:55 +02:00
5784e61e40 Update ChangeLog 2024-04-28 10:03:20 +02:00
63f88a2076 Minor fixes 2024-04-28 10:03:20 +02:00
Gregory Soutade
b8223a87ad Server: Improve URL match 2023-12-03 10:11:07 +01:00
129335dc2d Update manifest to v3 (Chrome only) 2022-10-01 14:33:42 +02:00
Gregory Soutade
2cb26d6d40 Remove alert() after password update. Replace it by button name update (2 seconds) 2022-09-26 20:28:22 +02:00
Gregory Soutade
54565f90b6 Remove alert() after password copy. Replace it by button name update (2 seconds) 2022-06-18 14:31:10 +02:00
Gregory Soutade
42142cbca9 Some enhancements in server interface :
* 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
2022-02-09 21:10:13 +01:00
Gregory Soutade
2def010612 Server: Underline current item 2022-01-23 09:48:18 +01:00
Gregory Soutade
daa7c3b44c Rework CSS 2021-12-23 21:25:44 +01:00
422178bc9d Update ChangeLog 2021-03-05 10:54:01 +01:00
736d717676 Server: Remove old v1 crypto functions & compatibility 2021-03-05 10:54:01 +01:00
7f95b19264 Add a checkbox to only copy password into clipboard from popup (and a default behaviour from options) 2021-03-03 14:53:52 +01:00
354ddbbba9 Update README 2021-03-03 14:53:52 +01:00
d25436f597 Update manifest 2021-03-03 14:53:52 +01:00
b79000accb Synchronize firefox webextension code 2021-03-03 14:53:48 +01:00
0e48a34d71 Add always_disabled feature 2021-03-03 14:53:19 +01:00
96b378695e Fix bug in gpass icon management. Don't call on_blur before unblock forms 2021-03-03 10:52:00 +01:00
a485267da8 Set activated icon only when a username is filled 2021-03-03 10:52:00 +01:00
25 changed files with 1209 additions and 696 deletions

View File

@@ -1,3 +1,42 @@
**v1.3 :**
Server
* Improve filter URL match
Addon
* Minor bug fixes
CLI
**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
* Remove old v1 crypto functions & compatibility
Addon
* Add always_disabled feature
* Fix bug in gpass icon management
* Set activated icon only when a username is filled
* Add a checkbox to only copy password into clipboard from popup (and a default behaviour from options)
CLI
**v1.0 :**
Server

View File

@@ -27,7 +27,7 @@ When you're in a login form and you want to use gPass, type your login (case sen
Another option is to enter your credentials in the new popup menu by clicking on gPass icon. If it's possible, gPass will auto fill password field, if not result password is stored into your clipboard. **Popup path is a safest method as website page will never see your masterkey.**
** Warning ** : Sometimes, addon could make some websites unusable, especially for login form. In this case, you can deactivate it for only one website by clicking right on gPass icon and "disable or enable gPass for this website". It's a local configuration, so it must be done for each browser.
** Warning ** : Sometimes, addon could make some websites unusable, especially for login form. In this case, you can deactivate it for only one website by clicking right on gPass icon and "disable or enable gPass for this website" in addon menu. It's a local configuration, so it must be done for each browser. gPass can also be disabled for ALL websites thanks to addon menu "Disable or enable gPass for ALL websites". _When gPass is disabled, you can still use popup feature_.
Technical details
@@ -81,19 +81,7 @@ The dependencies are libcurl and OpenSSL (-dev packages : ie _libcurl4-openssl-d
A sample configuration file is available _gpass.ini.sample_
Version Information
-------------------
Current version is 1.0.
License
-------
All the code is licensed under GPL v3. Source code is available [here](http://indefero.soutade.fr/p/gpass).
Mailing list
------------
You can subscribe to the announce list @ https://pannous.soutade.fr/lists/gpass
All the code is licensed under GPL v3. Source code is available [here](https://forge.soutade.fr/soutade/gPass).

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.
@@ -17,6 +17,11 @@
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 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<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 += "k" + key_index + "=" + a2hex(enc);
@@ -85,143 +90,147 @@ async function ask_server(logins, domain, wdomain, mkey, sendResponse, options)
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;
debug("connect to " + account_url);
const headers = {'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'};
const request = new Request(account_url, {method: 'POST', headers: headers, body: keys});
const response = await fetch(request).catch((e) => {
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<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("=");
if (params.length != 2 && params[0] != "<end>")
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);
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");
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",
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)
{
@@ -234,7 +243,11 @@ function update_gpass_icon(iconId, tabId)
break;
default:
}
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",
@@ -242,23 +255,43 @@ 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);
}
function is_gpass_enabled(uri)
async function is_gpass_enabled(uri)
{
var domain = parseURI.parseUri(uri);
domain = domain["host"];
debug("Is gpass enabled for " + domain + " ?");
return get_preference("disable-" + domain);
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(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);
var domain = parseUri(uri);
domain = domain["host"];
key = "disable-" + domain;
var key = "disable-" + domain;
if (enable)
{
debug("Enable gpass for " + domain);
@@ -267,7 +300,7 @@ function save_gpass_enable_config(uri, enable)
else
{
debug("Disable gpass for " + domain);
set_preference(key, true);
set_preference(key, true, null);
}
}
@@ -277,8 +310,8 @@ function _query_tabs_is_gpass_enabled(tabs, sendResponse)
{
is_gpass_enabled(tabs[0].url).then(
function (key_present) {
enabled = (key_present == null);
update_gpass_icon((enabled)?GPASS_ICON.ENABLED:GPASS_ICON.DISABLED, tabs[0].id);
var enabled = (key_present == null);
update_gpass_icon((enabled)?GPASS_ICON.NORMAL:GPASS_ICON.DISABLED, tabs[0].id);
sendResponse({"enabled":enabled});
}
);
@@ -300,38 +333,82 @@ function _query_tabs_update_icon(tabs, iconId)
}
}
function update_enable(enabled, tab, saveConfig)
{
var parameters;
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);
var enabled = (key_present == null);
// Do switch
enabled = !enabled;
if (enabled)
{
parameters = {type:"blockForms"};
debug("Now enabled");
}
else
{
parameters = {type:"unblockForms"};
debug("Now disabled");
}
save_gpass_enable_config(tab.url, enabled);
update_gpass_icon((enabled)?GPASS_ICON.ENABLED:GPASS_ICON.DISABLED, tab.id);
browser.tabs.sendMessage(tab.id, parameters);
update_enable(enabled, tab, true);
});
}
function createMenus(browser)
{
var title;
debug("Create menus");
/* Not supported by Chrome */
if (browser.menus.onShown)
title = 'Disable form\'s hook for this website';
else
title = 'Disable or enable form\'s hook 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 form\'s hook for ALL websites';
else
title = 'Disable or enable form\'s hook 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);
@@ -353,30 +430,29 @@ function extension_load()
});
return true;
}
else if (request.type == "is_gpass_enabled")
else if (request.type == "getPopupClipboard")
{
browser.tabs.query({active:true, currentWindow:true},
function cb(tabs) {
_query_tabs_is_gpass_enabled(tabs, sendResponse);
});
get_preference("popup_clipboard").then(
function (value) {
sendResponse({"value" : value});
});
return true;
}
else if (request.type == "switch_enable")
else if (request.type == "is_gpass_enabled")
{
debug("Switch enable");
browser.tabs.query({active:true, currentWindow:true},
function cb(tabs) {
_query_tabs_switch_enable(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
{
@@ -385,62 +461,88 @@ function extension_load()
}
);
/* Chrome */
if (!browser.menus && browser.contextMenus)
{
browser.menus = browser.contextMenus;
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';
browser.menus.create({
id: 'switch_enable',
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 'switch_enable':
gpass_switch_enable(tab);
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({active:true, currentWindow:true},
(tabs) => {
for (var i=0; i<tabs.length; i++)
update_enable(!always_disabled, tabs[i], false);
});
}
);
}
);
break;
case 'switch_enable':
gpass_switch_enable(tab);
break;
}
}
);
/* Firefox only */
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) {
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();
}
);
}
);
}
}
@@ -460,5 +562,4 @@ async function self_test()
}
//self_test();
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.
@@ -17,7 +17,13 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
function get_preference(key)
var default_preferences = {"pbkdf2_level": 1000,
"account_url": "https://gpass-demo.soutade.fr/demo",
"always_disabled":false,
"popup_clipboard":false
};
export function get_preference(key)
{
// Inspired from https://github.com/akiomik/chrome-storage-promise/
var promise = new Promise((resolve, reject) => {
@@ -44,21 +50,24 @@ function get_preference(key)
return promise;
}
function set_preference(key, value)
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);
if (sendResponse)
sendResponse(chrome.runtime.lastError);
});
}
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);
}

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.
@@ -17,30 +17,27 @@
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)
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<logins.length; i++)
@@ -56,23 +53,35 @@ function try_get_name(fields, type_filters, match)
for (var i=0; i<fields.length; i++)
{
var field = fields[i];
var to_test = !match;
for (var a=0; a<type_filters.length; a++)
{
if ((match && field.getAttribute("type") == type_filters[a]) ||
(!match && field.getAttribute("type") != type_filters[a]))
{
if (field.hasAttribute("name") && field.value != "")
{
name = field.getAttribute("name");
// Subset of common user field
if (name == "user") user = field.value;
else if (name == "usr") user = field.value;
else if (name == "username") user = field.value;
else if (name == "login") user = field.value;
if (match && field.getAttribute("type") == type_filters[a])
{
to_test = true;
break;
}
_add_name(all_logins, field.value);
}
if(!match && field.getAttribute("type") == type_filters[a])
{
to_test = false;
break;
}
}
if (to_test)
{
if (field.hasAttribute("name") && field.value != "")
{
var name = field.getAttribute("name");
// Subset of common user field
if (name == "user") user = field.value;
else if (name == "usr") user = field.value;
else if (name == "username") user = field.value;
else if (name == "login") user = field.value;
_add_name(all_logins, field.value);
}
}
}
@@ -83,28 +92,59 @@ function try_get_name(fields, type_filters, match)
return all_logins;
}
function get_logins(form, all_logins)
{
var fields = form.getElementsByTagName("input");
var logins = null;
debug("get_logins");
var type_filters = new Array();
// Get all <input type="text"> && <input type="email">
type_filters.push("text");
type_filters.push("email");
if (all_logins)
logins = try_get_name(fields, type_filters, true);
else
{
// Get all other fields except text, email and password
type_filters.push("password");
type_filters.push("hidden");
logins = try_get_name(fields, type_filters, false);
}
return logins;
}
function on_focus(e)
{
if (gpass_enabled)
if (!gpass_enabled)
return;
var logins = get_logins(this.form, true);
var all_logins = get_logins(this.form, false);
if (logins.length || all_logins.length)
{
parameters = {
var parameters = {
type:"update_icon",
icon_id:GPASS_ICON.ACTIVATED,
};
browser.runtime.sendMessage(parameters, {});
};
browser.runtime.sendMessage(parameters, {});
}
}
function on_blur(e)
{
if (gpass_enabled)
{
parameters = {
type:"update_icon",
icon_id:GPASS_ICON.NORMAL,
};
browser.runtime.sendMessage(parameters, {});
}
if (!gpass_enabled)
return;
var parameters = {
type:"update_icon",
icon_id:GPASS_ICON.NORMAL,
};
browser.runtime.sendMessage(parameters, {});
}
function on_sumbit(e)
@@ -116,15 +156,8 @@ function on_sumbit(e)
debug("on_submit");
type_filters = new Array();
// Get all <input type="text"> && <input type="email">
type_filters.push("text");
type_filters.push("email");
logins = try_get_name(fields, type_filters, true);
// Get all other fields except text, email and password
type_filters.push("password");
all_logins = try_get_name(fields, type_filters, false);
var logins = get_logins(form, true);
var all_logins = get_logins(form, false);
if (!logins.length)
logins = all_logins;
@@ -138,17 +171,17 @@ function on_sumbit(e)
if (field.getAttribute("type") == "password")
{
password = field.value;
var password = field.value;
if (!password.startsWith("@@") && !password.startsWith("@_"))
continue;
// Remove current value to limit master key stealing
field.value = "";
password_computed = true;
do_submit = !password.startsWith("@_");
mkey = password.substring(2);
var do_submit = !password.startsWith("@_");
var mkey = password.substring(2);
parameters = {
var parameters = {
type:"password",
logins:logins,
domain:domain,
@@ -159,18 +192,19 @@ function on_sumbit(e)
browser.runtime.sendMessage(parameters, {},
function (response) {
debug(response);
var field = fields[response.options.field_id];
switch(response.value)
{
case SERVER.OK:
var field = fields[response.options.field_id];
set_password(form, field, response.password, do_submit)
notify("Password successfully replaced", "");
break;
case SERVER.FAILED:
if (logins.length != all_logins.length)
if (logins.length != all_logins.length && all_logins.length != 0)
{
parameters[logins] = all_logins;
browser.runtime.sendMessage(parameters);
debug("Try with all logins");
parameters[logins] = all_logins;
browser.runtime.sendMessage(parameters);
}
break;
case SERVER.RESTART_REQUEST:
@@ -178,18 +212,18 @@ function on_sumbit(e)
break;
}
}
);
);
}
}
if (!password_computed)
{
debug("No password computed");
form.submit();
return true;
if (form.submit)
form.submit();
}
return false;
return true;
}
function set_password(form, field, password, do_submit)
@@ -200,7 +234,7 @@ function set_password(form, field, password, do_submit)
if (do_submit)
{
// Propagate change
change_cb = field.onchange;
var change_cb = field.onchange;
if (change_cb)
change_cb();
// Try to type "enter"
@@ -209,7 +243,8 @@ function set_password(form, field, password, do_submit)
evt.which = 13;
field.dispatchEvent(evt);
// Submit form
form.submit();
if (form.submit)
form.submit();
}
}
@@ -236,7 +271,7 @@ function block_all_forms(doc, do_block)
{
if (do_block)
{
old_cb = form.onsubmit;
var old_cb = form.onsubmit;
if (old_cb)
form.removeEventListener("submit", old_cb);
form.addEventListener("submit", on_sumbit);
@@ -259,8 +294,6 @@ function unblock_all_forms()
{
debug("unblock all forms");
on_blur(null);
for(var i=0; i<managed_forms.length; i++)
{
var form = managed_forms[i];
@@ -291,13 +324,13 @@ browser.runtime.onMessage.addListener(
debug("getUsername");
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">
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]);
@@ -313,12 +346,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<fields.length; a++)
for (var a=0; a<fields.length; a++)
{
field = fields[a];
var field = fields[a];
if (field.getAttribute("type") == "password")
{
if (password_field == null)
@@ -335,12 +368,11 @@ browser.runtime.onMessage.addListener(
if (password_field)
{
set_password(managed_forms[0], password_field,
request.password, request.submit);
request.password, request.submit);
response = "ok";
}
}
sendResponse(response);
return true;
}
else if (request.type == "blockForms")
{
@@ -350,11 +382,13 @@ browser.runtime.onMessage.addListener(
{
unblock_all_forms();
}
return true;
});
function document_loaded()
{
parameters = {
var parameters = {
"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.
@@ -19,17 +19,12 @@
var DEBUG = false;
SERVER = {OK : 0, FAILED : 1, RESTART_REQUEST : 2};
GPASS_ICON = {NORMAL:0, DISABLED:1, ACTIVATED:2};
export const SERVER = {OK : 0, FAILED : 1, RESTART_REQUEST : 2};
export const GPASS_ICON = {NORMAL:0, DISABLED:1, ACTIVATED:2};
var default_preferences = {"pbkdf2_level": 1000,
"account_url": "https://gpass-demo.soutade.fr/demo",
"crypto_v1_compatible": true};
export var browser = browser || chrome;
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}});
}
@@ -58,9 +53,9 @@ function str2ab(str) {
return bufView;
}
function crypto_pbkdf2(mkey, salt, level)
export function crypto_pbkdf2(crypto, mkey, salt, level)
{
AESCBC = {
var AESCBC = {
name: "AES-CBC",
length: 256,
}
@@ -87,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",
length: 256,
}
@@ -126,7 +121,7 @@ function _encrypt(mkey, iv, data)
data = str2ab(data);
promise = mkey.then(function(mkey){
var promise = mkey.then(function(mkey){
return crypto.subtle.encrypt({
name: "AES-CBC",
iv: iv
@@ -146,15 +141,15 @@ async function _decrypt(mkey, iv, data)
while ((data.length % 16))
data += "\0";
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 nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
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));
data = str2ab(data + pkcs7_padding);
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({
name: "AES-CBC",
iv: iv
@@ -169,13 +164,14 @@ async function _decrypt(mkey, iv, data)
return promise;
}
async function encrypt_ecb(mkey, data)
export async function encrypt_ecb(mkey, data)
{
var result = "";
var res;
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)
{
@@ -190,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)
{
@@ -211,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);
@@ -221,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);
@@ -231,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<parts.length; i++)
var res = "*.";
for (var i=1; i<parts.length; i++)
res += parts[i] + ".";
return res.substr(0, res.length-1);
}
@@ -258,14 +255,14 @@ function wildcard_domain(domain)
}
// http://stackoverflow.com/questions/3745666/how-to-convert-from-hex-to-ascii-in-javascript
function hex2a(hex) {
export 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_) {
export function a2hex(_str_) {
var hex = '';
for (var i = 0; i < _str_.length; i++)
{
@@ -276,8 +273,9 @@ function a2hex(_str_) {
return hex;
}
function debug(s)
export function debug(s)
{
if (DEBUG)
console.log(s);
}

View File

@@ -2,31 +2,29 @@
// (c) Steven Levithan <stevenlevithan.com>
// 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;
}
};

View File

@@ -1,43 +1,43 @@
{
"manifest_version": 2,
"manifest_version": 3,
"name": "gPass",
"short_name": "gPass",
"version": "1.0",
"version": "1.3",
"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é",
"homepage_url" : "http://indefero.soutade.fr/p/gpass",
"homepage_url" : "https://forge.soutade.fr/soutade/gPass",
"content_scripts": [
{
"matches": ["<all_urls>"],
"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": [
"<all_urls>"
],
"permissions": [
"<all_urls>",
"activeTab",
"notifications",
"webRequest",
"webRequestBlocking",
"tabs",
"storage",
"clipboardWrite",
"contextMenus"

View File

@@ -8,7 +8,11 @@
<b>Account URL</b> URL of your gPass account <input id="account_url" type="text"/><br />
<b>WARNING</b> It should be a valid HTTPS URL because navigator doesn't like mixed content (HTTPS/HTTP). If not, requests will silentely failed. If you have an auto-signed certificate, add it to trusted ones.<br/>
<br/>
<b>PBKDF2 level</b> Number of iterations used to derivate master key <input id="pbkdf2" type="number"/><br />
<b>PBKDF2 level</b> Number of iterations used to derivate master key <input id="pbkdf2_level" type="number"/><br />
<br/>
<b>Always disabled</b> Disable gPass for all websites. You can still use popup, but no hook is attached to login forms <input id="always_disabled" type="checkbox"/><br />
<br/>
<b>Popup clipboard copy</b> Copy password into clipboard (by default) when using popup <input id="popup_clipboard" type="checkbox"/><br />
<br/>
<br/>
<input type="button" id="save" value="Save"/>

View File

@@ -1,32 +1,46 @@
var default_preferences = {"pbkdf2_level": 1000,
"account_url": "https://gpass-demo.soutade.fr/demo"
"account_url": "https://gpass-demo.soutade.fr/demo",
"always_disabled":false,
"popup_clipboard":false
};
function save() {
var account_url = document.getElementById('account_url').value;
var pbkdf2 = document.getElementById('pbkdf2').value;
var pbkdf2_level = document.getElementById('pbkdf2_level').value;
var always_disabled = document.getElementById('always_disabled').checked;
var popup_clipboard = document.getElementById('popup_clipboard').checked;
chrome.storage.local.set({
'account_url': account_url,
'pbkdf2': pbkdf2,
'pbkdf2_level': pbkdf2_level,
'always_disabled': always_disabled,
'popup_clipboard': popup_clipboard,
}, function() {
alert('Saved');
});
}
function restoreOption(preferences, pref)
{
var res;
if (!preferences.hasOwnProperty(pref))
res = default_preferences[pref];
else
res = preferences[pref];
var element = document.getElementById(pref);
if (element.type == 'checkbox')
element.checked = res;
else
element.value = res;
}
chrome.storage.local.get(null, function(prefs) {
if (!prefs.hasOwnProperty("account_url"))
account_url = default_preferences['account_url'];
else
account_url = prefs['account_url'];
if (!prefs.hasOwnProperty("pbkdf2_level"))
pbkdf2 = default_preferences['pbkdf2_level'];
else
pbkdf2 = prefs['pbkdf2_level'];
document.getElementById('account_url').value = account_url;
document.getElementById('pbkdf2').value = pbkdf2;
restoreOption(prefs, 'account_url');
restoreOption(prefs, 'pbkdf2_level');
restoreOption(prefs, 'always_disabled');
restoreOption(prefs, 'popup_clipboard');
});
document.getElementById('save').addEventListener("click", save);

View File

@@ -6,10 +6,9 @@
<body>
<form id="passwordForm" autocomplete="off">
Username <input type="text" id="gPassUsername" autofocus></input><br/> Master key <input type="password" id="gPassMasterKey"/><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>
</form>
<script src="misc.js"></script>
<script src="compat.js"></script>
<script src="popup.js"></script>
<script type="module" src="popup.js"></script>
</body>
</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.
@@ -17,7 +17,55 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
var username_filled = false
import {SERVER, browser, notify, debug} from "./misc.js";
import {get_preference, send_tab_message} from "./compat.js";
var username_filled = false;
function _server_response(response, tabs, do_submit, force_copy)
{
debug("Get Response");
if (response.value == SERVER.OK)
{
/* Only copy */
if (document.getElementById("clipboard").checked)
{
navigator.clipboard.writeText(response.password).then(function() {
notify("Password pasted into clipboard", "");
window.setTimeout(function() {window.close();}, 2000);
});
return true;
}
/* Fill + optional copy */
var parameters = {
"type":"setPassword",
"password":response.password,
"submit":do_submit,
};
send_tab_message(tabs[0].id, parameters,
function(arg)
{
debug("Response to setPassword " + arg);
if (arg === "" || force_copy)
{
navigator.clipboard.writeText(response.password).then(function() {
notify("Password pasted into clipboard", "");
window.setTimeout(function() {window.close();}, 2000);
});
}
else
{
notify("Password filled", "");
window.setTimeout(function() {window.close();}, 2000);
}
}
);
}
return true;
}
function _query_tabs_get_password(tabs)
{
@@ -46,7 +94,7 @@ function _query_tabs_get_password(tabs)
var logins = new Array();
logins.push(username);
parameters = {
var parameters = {
type:"password",
logins:logins,
domain:domain,
@@ -55,38 +103,11 @@ function _query_tabs_get_password(tabs)
};
browser.runtime.sendMessage(parameters, {},
function (response)
{
debug("Get Response");
if (response.value == SERVER.OK)
{
parameters = {
"type":"setPassword",
"password":response.password,
"submit":do_submit
};
send_tab_message(tabs[0].id, parameters,
function(arg)
{
debug("Response to setPassword " + arg);
if (arg === "" || force_copy)
{
navigator.clipboard.writeText(response.password).then(function() {
notify("Password pasted into clipboard", "");
window.close();
});
}
else
{
notify("Password filled", "");
window.close();
}
}
);
}
return true;
});
function (response)
{
return _server_response(response, tabs, do_submit, force_copy);
}
);
return true;
}
@@ -97,12 +118,12 @@ function get_password(evt)
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;
}
pform = document.getElementById("passwordForm");
var pform = document.getElementById("passwordForm");
if (pform != null)
pform.onsubmit = get_password;
@@ -114,7 +135,7 @@ function _query_tabs_init(tabs)
if (tabs.length != 1) return;
/* Fill username */
parameters = {
var parameters = {
"type":"getUsername"
};
@@ -129,26 +150,40 @@ function _query_tabs_init(tabs)
}
});
/* Setup server link address */
/* Clipboard copy */
parameters = {
type:"getServerAddress"
"type":"getPopupClipboard"
};
browser.runtime.sendMessage(parameters, {},
function (response)
{
url = response.value;
url = url.substring(0, url.lastIndexOf('/'));
url += '?';
url += 'url=' + encodeURI(tabs[0].url.split("?")[0]);
url += '&user=' + document.getElementById("gPassUsername").value;
link = document.getElementById("serverLink");
link.href = url;
return true;
});
document.getElementById("clipboard").checked = response.value;
return true;
});
/* Setup server link address */
parameters = {
"type":"getServerAddress"
};
browser.runtime.sendMessage(parameters, {},
function (response)
{
if (tabs[0].url === undefined) return true;
if (tabs[0].url.startsWith("chrome://newtab/")) tabs[0].url = "";
var url = response.value;
url = url.substring(0, url.lastIndexOf('/'));
url += '?';
url += 'url=' + encodeURI(tabs[0].url.split("?")[0]);
url += '&user=' + document.getElementById("gPassUsername").value;
var link = document.getElementById("serverLink");
link.href = url;
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);

View File

@@ -27,10 +27,10 @@ function _notification(message, data)
message += data;
options = {
type: "basic",
title : "gPass",
type: "basic",
title : "gPass",
message : message,
iconUrl:browser.extension.getURL("icons/gpass_icon_64.png")
iconUrl: browser.runtime.getURL("icons/gpass_icon_64.png")
};
browser.notifications.create("gPass", options, function(){});
@@ -162,7 +162,7 @@ async function ask_server(logins, domain, wdomain, mkey, sendResponse, options)
{
debug("New pbkdf2 level " + server_pbkdf2_level);
pbkdf2_level = server_pbkdf2_level;
set_preference("pbkdf2_level", pbkdf2_level);
set_preference("pbkdf2_level", pbkdf2_level, null);
ret = SERVER.RESTART_REQUEST;
}
break;
@@ -234,6 +234,8 @@ function update_gpass_icon(iconId, tabId)
break;
default:
}
debug(icon_name);
icon_infos["path"] = {
16:"icons/gpass" + icon_name + "_icon_16.png",
@@ -245,12 +247,29 @@ function update_gpass_icon(iconId, tabId)
browser.browserAction.setIcon(icon_infos);
}
function is_gpass_enabled(uri)
async function is_gpass_enabled(uri)
{
var domain = parseURI.parseUri(uri);
domain = domain["host"];
debug("Is gpass enabled for " + domain + " ?");
return get_preference("disable-" + domain);
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)
@@ -267,7 +286,7 @@ function save_gpass_enable_config(uri, enable)
else
{
debug("Disable gpass for " + domain);
set_preference(key, true);
set_preference(key, true, null);
}
}
@@ -278,7 +297,7 @@ function _query_tabs_is_gpass_enabled(tabs, sendResponse)
is_gpass_enabled(tabs[0].url).then(
function (key_present) {
enabled = (key_present == null);
update_gpass_icon((enabled)?GPASS_ICON.ENABLED:GPASS_ICON.DISABLED, tabs[0].id);
update_gpass_icon((enabled)?GPASS_ICON.NORMAL:GPASS_ICON.DISABLED, tabs[0].id);
sendResponse({"enabled":enabled});
}
);
@@ -300,28 +319,36 @@ function _query_tabs_update_icon(tabs, iconId)
}
}
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;
if (enabled)
{
parameters = {type:"blockForms"};
debug("Now enabled");
}
else
{
parameters = {type:"unblockForms"};
debug("Now disabled");
}
save_gpass_enable_config(tab.url, enabled);
update_gpass_icon((enabled)?GPASS_ICON.ENABLED:GPASS_ICON.DISABLED, tab.id);
browser.tabs.sendMessage(tab.id, parameters);
update_enable(enabled, tab, true);
});
}
@@ -353,6 +380,14 @@ function extension_load()
});
return true;
}
else if (request.type == "getPopupClipboard")
{
get_preference("popup_clipboard").then(
function (value) {
sendResponse({"value" : value});
});
return true;
}
else if (request.type == "is_gpass_enabled")
{
browser.tabs.query({active:true, currentWindow:true},
@@ -361,15 +396,6 @@ function extension_load()
});
return true;
}
else if (request.type == "switch_enable")
{
debug("Switch enable");
browser.tabs.query({active:true, currentWindow:true},
function cb(tabs) {
_query_tabs_switch_enable(tabs, sendResponse)
});
return true;
}
else if (request.type == "update_icon")
{
debug("update_icon");
@@ -387,7 +413,8 @@ function extension_load()
if (!browser.menus && browser.contextMenus)
browser.menus = browser.contextMenus;
/* Settings */
browser.menus.create({
id: 'settings',
title: 'gPass Settings',
@@ -396,16 +423,30 @@ function extension_load()
/* Not supported by Chrome */
if (browser.menus.onShown)
title = 'disable gPass for this website';
title = 'Disable gPass for this website';
else
title = 'disable or enable gPass for this website';
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) {
@@ -413,6 +454,26 @@ function extension_load()
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;
@@ -428,9 +489,9 @@ function extension_load()
function (key_present) {
enabled = (key_present == null);
if (enabled)
title = 'disable gPass for this website';
title = 'Disable gPass for this website';
else
title = 'enable gPass for this website';
title = 'Enable gPass for this website';
browser.menus.update("switch_enable",
{
"title":title
@@ -441,6 +502,25 @@ function extension_load()
);
}
);
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();
}
);
}
);
}
}

View File

@@ -17,6 +17,12 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
var default_preferences = {"pbkdf2_level": 1000,
"account_url": "https://gpass-demo.soutade.fr/demo",
"always_disabled":false,
"popup_clipboard":false
};
function get_preference(key)
{
return browser.storage.local.get(key)
@@ -38,9 +44,11 @@ function get_preference(key)
);
}
function set_preference(key, value)
function set_preference(key, value, sendResponse)
{
browser.storage.local.set({[key]:value});
if (sendResponse)
sendResponse(true);
}
function delete_preference(key)

View File

@@ -25,10 +25,10 @@ function _notification(message, data)
message += data;
options = {
type: "basic",
title : "gPass",
type: "basic",
title : "gPass",
message : message,
iconUrl:browser.extension.getURL("icons/gpass_icon_64.png")
iconUrl: browser.runtime.getURL("icons/gpass_icon_64.png")
};
browser.notifications.create(options).then(
@@ -56,23 +56,35 @@ function try_get_name(fields, type_filters, match)
for (var i=0; i<fields.length; i++)
{
var field = fields[i];
var to_test = !match;
for (var a=0; a<type_filters.length; a++)
{
if ((match && field.getAttribute("type") == type_filters[a]) ||
(!match && field.getAttribute("type") != type_filters[a]))
{
if (field.hasAttribute("name") && field.value != "")
{
name = field.getAttribute("name");
// Subset of common user field
if (name == "user") user = field.value;
else if (name == "usr") user = field.value;
else if (name == "username") user = field.value;
else if (name == "login") user = field.value;
if (match && field.getAttribute("type") == type_filters[a])
{
to_test = true;
break;
}
_add_name(all_logins, field.value);
}
if(!match && field.getAttribute("type") == type_filters[a])
{
to_test = false;
break;
}
}
if (to_test)
{
if (field.hasAttribute("name") && field.value != "")
{
name = field.getAttribute("name");
// Subset of common user field
if (name == "user") user = field.value;
else if (name == "usr") user = field.value;
else if (name == "username") user = field.value;
else if (name == "login") user = field.value;
_add_name(all_logins, field.value);
}
}
}
@@ -83,28 +95,59 @@ function try_get_name(fields, type_filters, match)
return all_logins;
}
function get_logins(form, all_logins)
{
var fields = form.getElementsByTagName("input");
var logins = null;
debug("get_logins");
var type_filters = new Array();
// Get all <input type="text"> && <input type="email">
type_filters.push("text");
type_filters.push("email");
if (all_logins)
logins = try_get_name(fields, type_filters, true);
else
{
// Get all other fields except text, email and password
type_filters.push("password");
type_filters.push("hidden");
logins = try_get_name(fields, type_filters, false);
}
return logins;
}
function on_focus(e)
{
if (gpass_enabled)
if (!gpass_enabled)
return;
var logins = get_logins(this.form, true);
var all_logins = get_logins(this.form, false);
if (logins.length || all_logins.length)
{
parameters = {
parameters = {
type:"update_icon",
icon_id:GPASS_ICON.ACTIVATED,
};
browser.runtime.sendMessage(parameters, {});
};
browser.runtime.sendMessage(parameters, {});
}
}
function on_blur(e)
{
if (gpass_enabled)
{
parameters = {
type:"update_icon",
icon_id:GPASS_ICON.NORMAL,
};
browser.runtime.sendMessage(parameters, {});
}
if (!gpass_enabled)
return;
parameters = {
type:"update_icon",
icon_id:GPASS_ICON.NORMAL,
};
browser.runtime.sendMessage(parameters, {});
}
function on_sumbit(e)
@@ -116,15 +159,8 @@ function on_sumbit(e)
debug("on_submit");
type_filters = new Array();
// Get all <input type="text"> && <input type="email">
type_filters.push("text");
type_filters.push("email");
logins = try_get_name(fields, type_filters, true);
// Get all other fields except text, email and password
type_filters.push("password");
all_logins = try_get_name(fields, type_filters, false);
logins = get_logins(form, true);
all_logins = get_logins(form, false);
if (!logins.length)
logins = all_logins;
@@ -259,8 +295,6 @@ function unblock_all_forms()
{
debug("unblock all forms");
on_blur(null);
for(var i=0; i<managed_forms.length; i++)
{
var form = managed_forms[i];

View File

@@ -22,10 +22,6 @@ var DEBUG = false;
SERVER = {OK : 0, FAILED : 1, RESTART_REQUEST : 2};
GPASS_ICON = {NORMAL:0, DISABLED:1, ACTIVATED:2};
var default_preferences = {"pbkdf2_level": 1000,
"account_url": "https://gpass-demo.soutade.fr/demo",
"crypto_v1_compatible": true};
var browser = browser || chrome;
var crypto = crypto || window.crypto;

View File

@@ -3,11 +3,11 @@
"name": "gPass",
"short_name": "gPass",
"version": "1.0",
"version": "1.3",
"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é",
"homepage_url" : "http://indefero.soutade.fr/p/gpass",
"homepage_url" : "https://forge.soutade.fr/soutade/gPass",
"content_scripts": [
{
@@ -39,8 +39,6 @@
"<all_urls>",
"activeTab",
"notifications",
"webRequest",
"webRequestBlocking",
"tabs",
"storage",
"clipboardWrite",

View File

@@ -8,7 +8,11 @@
<b>Account URL</b> URL of your gPass account <input id="account_url" type="text"/><br />
<b>WARNING</b> It should be a valid HTTPS URL because navigator doesn't like mixed content (HTTPS/HTTP). If not, requests will silentely failed. If you have an auto-signed certificate, add it to trusted ones.<br/>
<br/>
<b>PBKDF2 level</b> Number of iterations used to derivate master key <input id="pbkdf2" type="number"/><br />
<b>PBKDF2 level</b> Number of iterations used to derivate master key <input id="pbkdf2_level" type="number"/><br />
<br/>
<b>Always disabled</b> Disable gPass for all websites. You can still use popup, but no hook is attached to login forms <input id="always_disabled" type="checkbox"/><br />
<br/>
<b>Popup clipboard copy</b> Copy password into clipboard (by default) when using popup <input id="popup_clipboard" type="checkbox"/><br />
<br/>
<br/>
<input type="button" id="save" value="Save"/>

View File

@@ -1,33 +1,51 @@
var default_preferences = {"pbkdf2_level": 1000,
"account_url": "https://gpass-demo.soutade.fr/demo"
"account_url": "https://gpass-demo.soutade.fr/demo",
"always_disabled":false,
"popup_clipboard":false
};
function save() {
var account_url = document.getElementById('account_url').value;
var pbkdf2 = document.getElementById('pbkdf2').value;
var pbkdf2_level = document.getElementById('pbkdf2_level').value;
var always_disabled = document.getElementById('always_disabled').checked;
var popup_clipboard = document.getElementById('popup_clipboard').checked;
browser.storage.local.set({
"account_url":account_url,
"pbkdf2_level":pbkdf2,
"pbkdf2_level":pbkdf2_level,
"always_disabled": always_disabled,
"popup_clipboard": popup_clipboard,
})
.then(function ok() { alert("Saved"); },
function err() { alert("Cannot save your preferences");}
);
}
function restoreOption(preferences, pref)
{
var res;
if (!preferences.hasOwnProperty(pref))
res = default_preferences[pref];
else
res = preferences[pref];
var element = document.getElementById(pref);
if (element.type == 'checkbox')
element.checked = res;
else
element.value = res;
}
function restoreOptions()
{
document.getElementById('account_url').value = default_preferences['account_url'];
document.getElementById('pbkdf2').value = default_preferences['pbkdf2_level'];
browser.storage.local.get().then(
function(prefs)
{
if (prefs.hasOwnProperty("account_url"))
document.getElementById('account_url').value = prefs["account_url"];
if (prefs.hasOwnProperty("pbkdf2_level"))
document.getElementById('pbkdf2').value = prefs["pbkdf2_level"];
restoreOption(prefs, 'account_url');
restoreOption(prefs, 'pbkdf2_level');
restoreOption(prefs, 'always_disabled');
restoreOption(prefs, 'popup_clipboard');
}
);
}

View File

@@ -6,6 +6,7 @@
<body>
<form id="passwordForm" autocomplete="off">
Username <input type="text" id="gPassUsername" autofocus></input><br/> Master key <input type="password" id="gPassMasterKey"/><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>
</form>
<script src="misc.js"></script>

View File

@@ -17,7 +17,52 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
var username_filled = false
var username_filled = false;
function _server_response(response, tabs, do_submit, force_copy)
{
debug("Get Response");
if (response.value == SERVER.OK)
{
/* Only copy */
if (document.getElementById("clipboard").checked)
{
navigator.clipboard.writeText(response.password).then(function() {
notify("Password pasted into clipboard", "");
window.close();
});
return true;
}
/* Fill + optional copy */
parameters = {
"type":"setPassword",
"password":response.password,
"submit":do_submit
};
send_tab_message(tabs[0].id, parameters,
function(arg)
{
debug("Response to setPassword " + arg);
if (arg === "" || force_copy)
{
navigator.clipboard.writeText(response.password).then(function() {
notify("Password pasted into clipboard", "");
window.close();
});
}
else
{
notify("Password filled", "");
window.close();
}
}
);
}
return true;
}
function _query_tabs_get_password(tabs)
{
@@ -55,38 +100,11 @@ function _query_tabs_get_password(tabs)
};
browser.runtime.sendMessage(parameters, {},
function (response)
{
debug("Get Response");
if (response.value == SERVER.OK)
{
parameters = {
"type":"setPassword",
"password":response.password,
"submit":do_submit
};
send_tab_message(tabs[0].id, parameters,
function(arg)
{
debug("Response to setPassword " + arg);
if (arg === "" || force_copy)
{
navigator.clipboard.writeText(response.password).then(function() {
notify("Password pasted into clipboard", "");
window.close();
});
}
else
{
notify("Password filled", "");
window.close();
}
}
);
}
return true;
});
function (response)
{
return _server_response(response, tabs, do_submit, force_copy);
}
);
return true;
}
@@ -129,9 +147,22 @@ function _query_tabs_init(tabs)
}
});
/* Clipboard copy */
parameters = {
"type":"getPopupClipboard"
};
browser.runtime.sendMessage(parameters, {},
function (response)
{
document.getElementById("clipboard").checked = response.value;
return true;
});
/* Setup server link address */
parameters = {
type:"getServerAddress"
"type":"getServerAddress"
};
browser.runtime.sendMessage(parameters, {},

View File

@@ -79,10 +79,4 @@ $REQUESTS_MIN_DELAY=1000;
*/
$CLEAR_TIME=15*60*1000;
/*
The first crypto schema use an AES-ECB process to encrypt logins.
It's used until version 0.7.
Since version 0.8, we use AES-CBC + SHA256.
*/
$CRYPTO_V1_COMPATIBLE=1;
?>

View File

@@ -73,13 +73,12 @@ else
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
<link rel="icon" type="image/png" href="resources/favicon.png" />
<link rel="stylesheet" type="text/css" href="resources/gpass.css" />
<link rel="icon" type="image/png" href="/resources/favicon.png" />
<link rel="stylesheet" type="text/css" href="/resources/gpass.css" />
<script language="javascript">
<?php
echo "pbkdf2_level=$PBKDF2_LEVEL; use_shadow_logins=$USE_SHADOW_LOGINS;\n";
echo "CLEAR_TIME=$CLEAR_TIME; // Clear master key after 15 minutes\n";
echo "CRYPTO_V1_COMPATIBLE=$CRYPTO_V1_COMPATIBLE;\n";
?>
document.addEventListener('DOMContentLoaded', function() {
window.onscroll = function(ev) {
@@ -96,7 +95,41 @@ else
setTimeout(scrollToTop, 24);
}
</script>
function enableMenu(elem, divFrom)
{
elem.style.display = "block";
divFrom.style['font-weight'] = "bold";
divFrom.style['text-decoration'] = "underline";
}
function disableMenu(elem, divFrom)
{
elem.style.display = "none";
divFrom.style['font-weight'] = "normal";
divFrom.style['text-decoration'] = "";
}
function switchMenuDisplay(id)
{
const array1 = ['admin', 'add_new_password', 'update_masterkey', 'export_database'];
for (const _id of array1)
{
elem = document.getElementById(_id);
divFrom = document.getElementById("menu_" + _id);
if (_id === id)
{
if (elem.style.display == "block")
disableMenu(elem, divFrom);
else
enableMenu(elem, divFrom);
}
else
disableMenu(elem, divFrom);
}
}
</script>
<script src="resources/misc.js"></script>
<script src="resources/gpass.js"></script>
<script src="resources/pwdmeter.js"></script>
@@ -104,9 +137,15 @@ else
</head>
<body onload="start();">
<div><a id="buttonTop" class="cInvisible" onclick="scrollToTop();"></a></div>
<div id="menu">
<div id="logo">
<a href="http://indefero.soutade.fr/p/gpass"><img src="resources/gpass.png" alt="logo"/></a>
</div>
<?php if ($ADMIN_MODE) echo "<div id=\"menu_admin\" onclick=\"switchMenuDisplay('admin');\">Create user</div>\n";?>
<div id="menu_add_new_password" onclick="switchMenuDisplay('add_new_password');">Add a new password</div>
<div id="menu_update_masterkey" onclick="switchMenuDisplay('update_masterkey');">Update master key</div>
<div id="menu_export_database" onclick="switchMenuDisplay('export_database');">Export database</div>
</div>
<div id="admin" <?php if (!$ADMIN_MODE) echo "style=\"display:none\"";?> >
<form method="post">
@@ -141,7 +180,7 @@ else
}
}
echo "</select>\n";
echo ' <b>Master key </b> <input id="master_key" type="password" onkeypress="if (event.keyCode == 13) update_master_key(true);"/>';
echo ' <b>Master key </b> <input id="master_key" type="password" onchange="update_master_key(true);"/>';
echo "<input type=\"button\" value=\"See\" onclick=\"update_master_key(true);\" />" . "\n";
if (!isset($_SERVER['HTTPS']))
@@ -156,12 +195,12 @@ else
if ($user != "")
{
echo "<b>Add a new password</b><br/>\n";
echo "<div class=\"title\">Add a new password</div>\n";
echo 'URL <input type="text" id="new_url" name="url" value="' . (filter_input(INPUT_GET, "url", FILTER_SANITIZE_SPECIAL_CHARS) ?: "") . '"/>';
echo 'URL <input type="text" id="new_url" name="url" value="' . (parse_url(filter_input(INPUT_GET, "url", FILTER_SANITIZE_SPECIAL_CHARS))['host'] ?: "") . '"/>';
echo 'login <input type="text" id="new_login" name="login" value="' . (filter_input(INPUT_GET, "user", FILTER_SANITIZE_SPECIAL_CHARS) ?: "") . '"/>';
echo 'password <input id="new_password" type="text" name="password"/>';
echo 'master key <input type="text" name="mkey" id="new_mkey" onkeypress="if (event.keyCode == 13) add_password();" onkeyup="chkPass(this.value);"/>';
echo 'master key <input type="text" name="mkey" id="new_mkey" onchange="add_password();" onkeyup="chkPass(this.value);"/>';
echo '<input type="button" value="Generate password" onClick="generate_password();"/>';
echo '<input type="button" value="Generate simple password" onClick="generate_simple_password();"/>';
echo "<input type=\"button\" name=\"add\" value=\"Add\" onclick=\"add_password();\"/>";
@@ -171,15 +210,13 @@ if ($user != "")
}
?>
</div>
<div id="passwords">
</div>
<div id="update_masterkey">
<?php
global $user;
if ($user != "")
{
echo "<b>Update Masterkey</b><br/>\n";
echo "<div class=\"title\">Update Masterkey</div>\n";
echo 'Old master key <input type="text" id="oldmkey"/>';
echo 'New master key <input type="text" id="newmkey" onkeyup="chkPass(this.value);"/>';
@@ -193,13 +230,20 @@ if ($user != "")
if ($user != "")
{
echo "<b>Export</b><br/>\n";
echo "<div class=\"title\">Export</div>\n";
echo '<input type="button" value="Export" onclick="export_database();"/>';
echo '<a id="export_link">Download</a>';
}
?>
</div>
</div>
<div id="filter">
Filter <input id='password_filter' value=<?php echo "'" . (parse_url(filter_input(INPUT_GET, "url", FILTER_SANITIZE_SPECIAL_CHARS))['host'] ?: "") . "'" ?> onchange='password_filter_changed();'/>
<input type="button" onclick="password_filter_changed();" value="Apply"/>
<input type="button" onclick="document.getElementById('password_filter').value = '';password_filter_changed();" value="Clear"/>
</div>
<div id="passwords"></div>
</div>
</body>
</html>

View File

@@ -1,34 +1,61 @@
body {
background-image:linear-gradient(#0096ff 30%, white);
background-color:#2d2e44;
height:100%; width:100%;
color:white;
}
input {
opacity:0.9;
}
#logo {
display:inline;
}
#logo img{
display:inline;
width:250px;
height:170px;
margin-right:4%;
}
#menu {
display:block;
margin-left:auto;
margin-right:auto;
margin-top:30px;
margin-bottom:40px;
text-align:center;
padding-bottom:10pt;
}
#menu div {
display:inline;
margin-right:2%;
font-size:x-large;
cursor: pointer;
}
.title {
font-size:xx-large;
font-weight:bold;
margin-bottom:20pt;
}
#admin {
border-style:solid;
border-width:5px;
border-color:red;
padding : 15px;
margin : 15px;
border-radius: 25px;
border-style:solid;
border-width:5px;
border-color:grey;
display:none;
}
#admin form {
text-align : center;
}
#admin input {
margin-right:10px;
}
#user {
border-style:solid;
border-width:5px;
border-color:green;
padding : 15px;
margin : 15px;
text-align:center;
@@ -45,10 +72,12 @@ body {
text-align : center;
}
#selected_user {
margin-left:10px;
margin-right:10px;
}
#passwords {
border-style:solid;
border-width:5px;
border-color:grey;
padding : 15px;
margin : 15px;
}
@@ -57,34 +86,37 @@ body {
padding:10px;
text-align:center;
font-size:xx-large;
}
.hash {
width : 700px;
color:#de0036;
}
#add_new_password {
border-radius: 25px;
border-style:solid;
border-width:5px;
border-color:blue;
border-color:grey;
padding : 15px;
margin : 15px;
display:none;
}
#update_masterkey {
border-style:solid;
border-width:5px;
border-color:yellow;
padding : 15px;
margin : 15px;
border-radius: 25px;
border-style:solid;
border-width:5px;
border-color:grey;
display:none;
}
#export_database {
border-style:solid;
border-width:5px;
border-color:pink;
padding : 15px;
margin : 15px;
border-radius: 25px;
border-style:solid;
border-width:5px;
border-color:grey;
display:none;
}
#export_link {
@@ -92,6 +124,10 @@ body {
visibility:hidden;
}
#filter {
font-weight:bold;
}
.error {
text-align:center;
color:red;

View File

@@ -121,7 +121,6 @@ function clear_form()
inputs[i].type == "password")
inputs[i].value = "";
}
chkPass("");
}
function url_domain(data) {
@@ -161,6 +160,8 @@ var current_mkey = "";
var clearTimer = null;
var global_iv = null;
var server_url = window.location.href.split('?')[0];
var clear_passwords = new Array();
var block_filter = true;
function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
this.ciphered_login = ciphered_login;
@@ -226,7 +227,6 @@ function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
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));
@@ -238,38 +238,16 @@ function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
{
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.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;
@@ -449,6 +427,51 @@ async function get_ciphered_credentials(masterkey)
req.send("get_secure_passwords=1&user=" + current_user + "&access_tokens=" + access_tokens);
}
function password_filter_changed()
{
if (block_filter) return;
filter = document.getElementById('password_filter').value ;
if (filter !== '')
filter = '.*' + filter + '.*';
password_div = document.getElementById("clear_passwords");
password_div.removeAllChilds();
for (idx in clear_passwords)
{
div = clear_passwords[idx];
if (typeof(div) === 'function')
continue;
if (filter === '')
password_div.appendChild(div);
else
{
childs = clear_passwords[idx].children;
for (elem_idx in childs)
{
elem = childs[elem_idx];
if (elem.name == 'url')
{
// Remove * for wildcards domains
target_url = elem.value.replace('*', '') ;
try {
if (target_url.match(filter) || filter.match(target_url))
password_div.appendChild(div);
}
/* Forgive re errors */
catch(error)
{
// console.log(error);
}
break;
}
}
}
}
}
async function change_master_key(warning_unciphered)
{
var nb_unciphered = 0;
@@ -462,9 +485,6 @@ async function change_master_key(warning_unciphered)
nb_unciphered++;
}
if (!nb_unciphered && warning_unciphered)
alert("No password unciphered with this master key !");
password_div = document.getElementById("passwords");
password_div.removeAllChilds();
@@ -472,6 +492,11 @@ async function change_master_key(warning_unciphered)
div.setAttribute("id", "nb_unciphered_passwords");
password_div.appendChild(div);
div = document.createElement("div");
div.setAttribute("id", "clear_passwords");
password_div.appendChild(div);
clear_passwords = new Array();
for(i=0; i<passwords.length; i++)
{
if (passwords[i].isUnciphered(current_mkey))
@@ -517,13 +542,23 @@ async function change_master_key(warning_unciphered)
update_button = document.createElement("input");
update_button.setAttribute("type", "button");
update_button.setAttribute("value", "Update");
update_button.setAttribute("onclick", "update_entry(\"unciph_entry_" + i + "\");");
update_button.setAttribute("onclick", "update_entry(this, \"unciph_entry_" + i + "\");");
div.appendChild(update_button);
password_div.appendChild(div);
clipboard_button = document.createElement("input");
clipboard_button.setAttribute("type", "button");
clipboard_button.setAttribute("name", "copy_button");
clipboard_button.setAttribute("value", "Copy password");
clipboard_button.setAttribute("onclick", "copy_clipboard(this, \"unciph_entry_" + i + "\");");
div.appendChild(clipboard_button);
clear_passwords.push(div);
}
}
if (warning_unciphered && !nb_unciphered)
alert("No password unciphered with this master key !");
div = document.createElement("div");
div.setAttribute("id", "nb_ciphered_passwords");
password_div.appendChild(div);
@@ -546,13 +581,15 @@ async function change_master_key(warning_unciphered)
url.setAttribute("class", "hash");
url.setAttribute("type", "text");
url.setAttribute("name", "URL");
url.setAttribute("size", "32");
div.appendChild(url);
div.appendChild(document.createTextNode("password"));
password = document.createElement("input");
password.setAttribute("class", "hash");
password.setAttribute("class", "password_hash");
password.setAttribute("type", "text");
password.setAttribute("name", "password");
password.setAttribute("size", "40");
div.appendChild(password);
delete_button = document.createElement("input");
@@ -578,41 +615,12 @@ async function change_master_key(warning_unciphered)
}
}
cur_url = document.getElementById("new_url").value;
/* If we have a current URL in add form and we have a password entry that match this URL, go to the last */
/* Can't do this before, because everything is not displayed from browser */
if (cur_url !== "")
{
cur_url = url_domain(cur_url);
for(i=0; i<passwords.length; i++)
{
if (!passwords[i].isUnciphered(current_mkey))
continue;
url_elem = document.getElementById("unciph_url_" + i);
target_url = url_elem.value;
// Replace wildcard domain by .*<domain>
if (target_url[0] == "*")
target_url = "." + target_url;
try {
if (cur_url.match(target_url))
{
window.scrollTo(0, url_elem.offsetTop);
break;
}
}
/* Forgive re errors */
catch(error)
{
//console.log(error);
}
}
}
input = document.getElementById("master_key");
input.value = "";
update_stats();
block_filter = false;
password_filter_changed();
}
function update_master_key(warning_unciphered)
@@ -660,7 +668,11 @@ function update_master_key(warning_unciphered)
function start()
{
select_widget = document.getElementById('selected_user') ;
new_url = document.getElementById('new_url') ;
if (new_url.value !== "")
switchMenuDisplay('add_new_password');
select_widget = document.getElementById('selected_user') ;
if (select_widget == null) return;
@@ -800,16 +812,14 @@ function add_password()
for(i=0; i<inputs.length; i++)
{
if (inputs[i].getAttribute("type") == "text" ||
inputs[i].getAttribute("type") == "password")
name = inputs[i].getAttribute("name");
if (name === "password" || name === "mkey")
inputs[i].value = "";
}
startClearTimer();
});
window.scrollTo(0,document.body.scrollHeight);
return true;
}
@@ -874,7 +884,7 @@ function delete_entry(entry_number)
update_stats();
}
function update_entry(entry_number)
function update_entry(button, entry_number)
{
var url = "";
var login = "";
@@ -938,7 +948,14 @@ function update_entry(entry_number)
passwords[found] = pentry;
ciphered_login.setAttribute("login", pentry.ciphered_login);
alert("Entry updated");
if (button !== null)
{
old_value = button.value;
button.value = "Updated";
window.setTimeout(() => { button.value = old_value; }, 2000);
}
else
alert("Entry updated");
});
}
@@ -1005,6 +1022,39 @@ async function update_masterkey()
}
}
function copy_clipboard(button, entry_number)
{
var password = "";
entry = document.getElementById(entry_number);
if (entry == null) {
alert(entry_number + " not found");
return;
}
inputs = entry.getElementsByTagName("input");
for(i=0; i<inputs.length; i++)
{
if (inputs[i].getAttribute("name") == "password")
{
inputs[i].select();
document.execCommand('copy');
inputs[i].setSelectionRange(0,0); // Unselect
if (button !== null)
{
old_value = button.value;
button.value = "Copied";
window.setTimeout(() => { button.value = old_value; }, 2000);
}
else
alert('Password copied into clipboard');
break;
}
}
}
function makeText(text) {
var data = new Blob([text], {type: 'application/xml'});