3 Commits
Author SHA1 Message Date
soutade 09c8d6bcba Merge branch 'master' of soutade.fr:gpass into HEAD 2018-06-30 09:08:39 +02:00
soutade 07c64a44a0 Fix addon :
* Error in wildcard domain generation
  * Forgot some console.log in code (replace it with debug function)
2018-06-30 09:04:40 +02:00
Gregory Soutade 0a233a42c0 Update ChangeLog 2017-12-15 09:44:27 +01:00
59 changed files with 2333 additions and 3167 deletions
+4 -70
View File
@@ -1,76 +1,10 @@
**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 :**
**v0.8.2 :**
Server
* Add clear form button for "Add new password" div
* If we have a current URL in add form and we have a password entry that match this URL, go to the last when all is deciphered
Addon
* Remove old v1 crypto functions & compatibility
* Remove old firefox addon code
* Remove block_url feature to increase websites compatibility
* Force copy password into clipboard for @_ request from popup even if we can fill it
* Fix a bug: wait for promise before displaying message for clipboard pasted password
CLI
**v0.9 :**
Server
* Add support for user & url parameters from gPass popup
* Fix some minor bugs
Addon
* Move all core functions from mains.js to background.js
* Use message interface for IPCs between main.js, background.js and popup.js
* Add popup interface :
* Safest method to compute masterkey
* Direct access to our own gPass server with auto URL and username fill
* Add some specific menus :
* Access to gPass settings
* Allow to disable extension
* Update gPass icon when a password field has focus and gPass is ready to work
* Add Privacy Policy information
* Error in wildcard domain generation
* Forgot some console.log in code (replace it with debug function)
CLI
@@ -79,11 +13,11 @@ CLI
Server
* Scroll to page bottom when user adds a new password
* Run a simpler algorithm for wildcard domains
Addon
* Add Privacy Policy information
* Run a simpler algorithm for wildcard domains
CLI
+13 -13
View File
@@ -2,18 +2,18 @@ gPass web browser extension Privacy Policy
------------------------------------------
## Information we collect ##
## Information we collect ##
The gPass extension collect three information once invoked :
* Site address URL
* Login name
* Master key
* Login name
* Master key
## How we use information we collect ##
## How we use information we collect ##
Once collected, site address and login name are encrypted by a derived version of your master key.
It's then sent to the server (password server) you configured in extension configuration page for comparison.
Once collected, site address and login name are crypted by a derived version of your master key.
It's then sent to the server you configured in extension configuration page for comparison.
This server has been set up by the user himself (recommended) or by a provider he trust in.
@@ -21,27 +21,27 @@ The database that the server access to do comparisons only contains the crypted
version of your information. They are never decrypted in the server side.
If a comparison match, the real password is sent back to your extension were
it's unencrypted using the same key (derived masterkey).
it's unencrypted using the same key.
Finally, the application context is cleared and nothing is kept in memory
Finally, the application context is cleared and nothing is retained in memory
nor written anywhere.
## Accessing and updating your personal information ##
As a user, you can add, edit and delete your ciphered information through
the web interface of the password server.
As a user, you can add, edit and delete your crypted information through
the web interface of the configuration defined server.
During these operations, no clear information is sent to the server.
## Information we share ##
Nothing is shared with anyone. Nor on extension side, nor on server side.
Nothing is shared with anyone. Nor on extension side nor on server side.
## Information security ##
## Information security ##
Information transmitted to the server are done through an HTTPS AJAX request.
Data are encrypted using AES 256 CBC algorithm and the master key is prior
Data are crypted using AES 256 CBC algorithm and the master key is prior
derived using PKBDF2 algorithm.
+26 -22
View File
@@ -9,9 +9,9 @@ Everyday we have a lot of passwords to manage corresponding to a lot of accounts
* Password are not very strong
* We use them for multiple accounts
The best way to avoid these errors is to have a unique strong password for each account. gPass helps to reach this goal : you keep a subset of passwords (called masterkey) and for each login/masterkey tuple you chose, gPass returns the real password by querying a password server.
The best way to avoid these errors is to have a unique strong password for each account. gPass helps to reach this goal : you keep a subset of passwords (called masterkey) and for each login/password tuple you chose, gPass returns the real password by querying a password server.
To have a high level of security, all information is stored encrypted (server side). Nothing is stored on client. The decryption is done on the fly when it's needed and only with user input. So, a hacker can get your password database, it will not be able to see any information (except if it brute force or leak your masterkey) ! So it's important to choose a strong masterkey !
To have a high level of security, all information is stored encrypted (server side). Nothing is stored on client. The decryption is done on the fly when it's needed and only with user input. So, a hacker can get your password database, it will not be able to see any information (except if it brute force your masterkey) ! So it's important to choose to strong masterkey !
This addon is like [last pass](https://lastpass.com/) one, but I wanted it to be open source and self hostable (be careful on server down !). Moreover, with gPass, you can have multiple master keys !
@@ -19,26 +19,21 @@ This addon is like [last pass](https://lastpass.com/) one, but I wanted it to be
Usage
-----
The first thing to do is to populate your database (from your/a password server) with website address/login/password/master key values. You can use "*" character to access to all subdomains of a specific website (ie *.google.com). If you want to make a strong password, there is a password generator. After that, configure your addon in "tools -> addons -> gPass -> preferences" in Firefox or "More tools -> extensions -> gPass -> options" in Chrome to point to your password server (+ username). **Don't forget to enable addon within private mode**. Be careful, login and password are case sensitive !
The first thing to do is to populate your database (from your/a password server) with website/login/password/master key values. You can use "*" character to access to all sub domains of a specific website. If you want to make strong password, there is a password generator. After that, configure your addon in "tools -> addons -> gPass -> preferences" in Firefox or "addons -> gPass -> options" in Chrome to point to your password server (+ username). Be careful, login and password are case sensitive.
When you're in a login form and you want to use gPass, type your login (case sensitive !) and fill "@@masterkey" in password field (only if gPass icon is green !). Then submit and password will automatically be replaced by the one in the database (after addon decrypt it).
**You can also type "@_masterkey" to only replace your password without automatic submit. This allows to support more websites.**
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" 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_.
When you're in a login form and you want to use gPass, type your login (case sensitive !) and fill "@@masterkey" in password field. Then submit and password will automatically be replaced by the one in the database (after addon decrypt it).
**You can also type "@_masterkey" to only replace your password without submitting and manually submit. This allows to support more websites.**
Technical details
-----------------
The two main columns in database are "login" and "password".
login is compounded by "domain;login", salted and encrypted with AES 256-CBC
The two columns in database are "login" and "password".
login is compounded by "domain;login" is salted and encrypted with AES 256-CBC
The real key that encrypts these fields is PBKDF2 (hmac-sha256, masterkey, password_server_url, 1000, 256), IV is PBKDF2 (hmac-sha256, password_server_url, masterkey, 1000, 256)
The key that encrypt these fields is PBKDF2 (hmac-sha256, masterkey, password_server_url, 1000, 256), IV is PBKDF2 (hmac-sha256, password_server_url, masterkey, 1000, 256)
PBKDF2 level can be changed by user.
PBKDF2 level can be changed by user
Server side is written in PHP (with SQLite3 for database component).
@@ -46,27 +41,25 @@ Server side is written in PHP (with SQLite3 for database component).
Server
------
To host a password server, you need a webserver. Just copy server files in a directory read/write for web server user (www-data). A sample apache2 configuration file is available in resources. Since v0.8 and the use of Crypto API, **it's manadatory to have an HTTPS access (valid SSL/TLS certificate) to the server**. Without that, the decryption will fails.
To host a password server, you need a webserver. Just copy server files in a directory read/write for web server user (www-data). A sample apache2 configuration file is available in resources. Since v0.8 and the use of Crypto API, it's manadatory to have an HTTPS access to the server. Without that, the decryption will fails.
Configuration parameters are in conf.php
A demonstration server is available [here](https://gpass-demo.soutade.fr). It's the default server configuration for fresh installed addon (user demo).
A demonstration server is available [here](https://gpass-demo.soutade.fr). It may not works with HTTPS content because it uses a self-signed SSL certificate. If so, explicitly add the certificate to your browser. It's the default server of package (user demo).
**Warning** The master key derivation is partially based on account URL. So it's linked to your current server information. You can't move databases from servers with different URLs, you need to export them and import it again.
**Warning** The master key derivation is partially based on account URL. So it's linked to your current server information. You can't move databases from servers with different URLs, you need to export them and import again.
**Server side is available [here](http://indefero.soutade.fr/p/gpass/downloads)**
Version 0.6 introduces shadow logins. It's a protection again illegal database dump and purge but requires twice computation. Database update is transparent.
The principle is to generate a random value (shadow login) that must be encrypted with the masterkey to get an access token. This access token allows to get the true (but encrypted) login/password couple. It's a kind of challenge : if I can encrypt the shadow login, I know the masterkey ! For security reason, the derivation of masterkey for deciphering passwords is different than for encrypting shadow logins (it uses its own salt). It's enabled by default.
Version 0.6 introduces shadow logins. It's a protection again illegal database dump and purge but requires high cpu bandwidth. Database update is transparent.
The principle is to generate a random value (shadow login) that must be encrypted with the masterkey to get an access token. This access token allows to get the true (but encrypted) login/password couple. It's a kind of challenge : if I can encrypt the shadow login, I know the masterkey ! For security reason, the derivation of masterkey for deciphering passwords is different than for encrypting shadow logins (it uses its own salt).
Client
------
Just install the package. You can have debug information by setting DEBUG in main.js.
Command line interface
----------------------
@@ -80,8 +73,19 @@ 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 0.8.2 **(not compatible with 0.7)**
Firefox will remove support for addons, so the gPass addon code is not supported since v0.8, please migrate to webextension.
Transition from v0.7 to v0.8 : **Please update your masterkey (even with the same one) to gain a security level of your passwords's wallet.**
**This version is incompatible from 0.1**. Please use [this script](http://soutade.fr/files/gpass_migrate_0_1.php) to migrate.
License
-------
All the code is licensed under GPL v3. Source code is available [here](https://forge.soutade.fr/soutade/gPass).
All the code is licensed under GPL v3. Source code is available [here](http://indefero.soutade.fr/p/gpass).
+67 -537
View File
@@ -1,565 +1,95 @@
/*
Copyright (C) 2013-2022 Grégory Soutadé
Copyright (C) 2013-2017 Grégory Soutadé
This file is part of gPass.
gPass is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gPass is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
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;
function _notification(message, data)
function url_block_callback(details)
{
if (message !== data)
message += data;
var options = {
type: "basic",
title : "gPass",
message : message,
iconUrl:"icons/gpass_icon_64.png"
};
browser.notifications.create("gPass", options, function(){});
}
async function generate_request(domain, login, mkey, iv)
{
var v = domain + ";" + login;
debug("will encrypt " + v);
while ((v.length % 16))
v += "\0";
var hash = await digest(crypto, v);
v += hash.slice(8, 24);
var enc = encrypt_cbc(mkey, iv, v);
return enc;
}
async function ask_server(logins, domain, wdomain, mkey, sendResponse, options)
{
var account_url = await get_preference("account_url");
var salt = parseUri(account_url);
salt = salt["host"] + salt["path"];
debug("salt " + salt);
var pbkdf2_level = await get_preference("pbkdf2_level");
var global_iv = await simple_pbkdf2(crypto, salt, mkey, pbkdf2_level);
global_iv = global_iv.slice(0, 16);
var mkey = crypto_pbkdf2(crypto, mkey, salt, pbkdf2_level);
debug("global_iv " + a2hex(global_iv));
var keys = "";
var key_index;
var a;
for(key_index=0, a=0; a<logins.length; a++, key_index++)
// console.log(JSON.stringify(details));
if (details.requestBody)
{
var enc = await generate_request(domain, logins[a], mkey, global_iv);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + key_index + "=" + a2hex(enc);
if (wdomain != "")
if (details.requestBody.formData)
{
enc = await generate_request(wdomain, logins[a], mkey, global_iv);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + (++key_index) + "=" + a2hex(enc);
}
}
debug("Keys " + keys);
var ret = SERVER.OK;
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", "");
});
if (response === undefined)
{
ret = SERVER.ERROR;
sendResponse({"value": ret, options:options});
return;
}
const responseText = await response.text();
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>")
{
_notification("Error : It seems that it's not a gPass server",
responseText);
ret = SERVER.FAILED;
break;
}
switch(params[0])
{
case "protocol":
debug("protocol : " + params[1]);
if (params[1].indexOf("gpass-") != 0)
for (var key in details.requestBody.formData)
{
_notification("Error : It seems that it's not a gPass server",
responseText);
ret = SERVER.FAILED;
break;
}
var 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)
for(var idx in details.requestBody.formData[key])
{
case 2:
server_pbkdf2_level = 1000;
break;
case 3:
// Version 3 : nothing special to do
case 4:
// Version 4 : nothing special to do
break;
value = details.requestBody.formData[key][idx];
if (value.startsWith("@@") ||
value.startsWith("@_"))
return {cancel: true};
}
}
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;
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});
}
else
{
debug("No password found");
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");
var icon_infos = {"tabId":tabId};
var icon_name = "";
switch (iconId)
{
case GPASS_ICON.NORMAL: break;
case GPASS_ICON.DISABLED:
icon_name = "_disabled";
break;
case GPASS_ICON.ACTIVATED:
icon_name = "_activated";
break;
default:
}
debug(icon_name);
var icon_infos = {};
icon_infos["path"] = {
16:"icons/gpass" + icon_name + "_icon_16.png",
32:"icons/gpass" + icon_name + "_icon_32.png",
64:"icons/gpass" + icon_name + "_icon_64.png",
128:"icons/gpass" + icon_name + "_icon_128.png",
};
if (browser.browserAction)
browser.browserAction.setIcon(icon_infos);
else
browser.action.setIcon(icon_infos);
}
async function is_gpass_enabled(uri)
{
return await get_preference("always_disabled").then(
function(always_disabled) {
if (always_disabled)
{
debug("Always disabled");
return new Promise(function(resolve, reject) {
resolve(1); // null -> enabled, 1 -> disabled
});
}
else
{
debug("Check for enable");
var domain = parseUri(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(uri);
domain = domain["host"];
var key = "disable-" + domain;
if (enable)
{
debug("Enable gpass for " + domain);
delete_preference(key);
}
else
{
debug("Disable gpass for " + domain);
set_preference(key, true, null);
}
}
function _query_tabs_is_gpass_enabled(tabs, sendResponse)
{
if (tabs.length)
{
is_gpass_enabled(tabs[0].url).then(
function (key_present) {
var enabled = (key_present == null);
update_gpass_icon((enabled)?GPASS_ICON.NORMAL:GPASS_ICON.DISABLED, tabs[0].id);
sendResponse({"enabled":enabled});
}
);
}
else
{
debug("No cur tab");
sendResponse({"enabled":true});
}
return true;
}
function _query_tabs_update_icon(tabs, iconId)
{
if (tabs.length)
{
update_gpass_icon(iconId, tabs[0].id);
}
}
function update_enable(enabled, tab, saveConfig)
{
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)
/*
// Analyse POST parameters
if (details.method == "POST" && details.requestBody.raw)
{
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 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(request.domain);
domain = domain["host"];
var wdomain = wildcard_domain(domain);
ask_server(request.logins, domain,
wdomain, request.mkey,
sendResponse, request.options);
return true;
}
else if (request.type == "notification")
{
_notification(request.options.message, request.options.data);
}
else if (request.type == "getServerAddress")
{
get_preference("account_url").then(
function (address) {
sendResponse({"value" : address});
});
return true;
}
else if (request.type == "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}).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}).then( (tabs) =>
{
_query_tabs_update_icon(tabs, request.icon_id);
});
}
else
{
debug("Unknown message " + request.type);
}
alert(details.requestBody.raw);
var postedString = decodeURIComponent(String.fromCharCode.apply(null,
new Uint8Array(details.requestBody.raw[0].bytes)));
if (postedString.indexOf("=@@") != -1 ||
postedString.indexOf("=@_") != -1)
return {cancel: true};
}
);
/* Chrome */
if (!browser.menus && browser.contextMenus)
{
browser.menus = browser.contextMenus;
*/
}
browser.runtime.onInstalled.addListener(() => {
createMenus(browser)
return {cancel: false};
}
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.type == "notification")
{
options = {
type: "basic",
title : "gPass",
message : request.options.message,
iconUrl:chrome.extension.getURL("icons/gpass_icon_64.png")
};
chrome.notifications.create("gPass", options, function(){});
window.setTimeout(function() {chrome.notifications.clear("gPass", function(){})}, 2000);
}
else if (request.type == "block_url")
{
chrome.tabs.getCurrent(function cb(tab) {
if (tab)
{
chrome.webRequest.onBeforeRequest.addListener(
url_block_callback,
{urls:[request.options.url],
"types":["main_frame"],
"tabId":tab.id,
"windowId":tab.windowId
},
["blocking", "requestBody"]);
}
else
{
chrome.webRequest.onBeforeRequest.addListener(
url_block_callback,
{urls:[request.options.url], "types":["main_frame"]},
["blocking", "requestBody"]);
}
});
}
});
browser.menus.onClicked.addListener(
function(info, tab) {
switch (info.menuItemId) {
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) {
get_preference('always_disabled').then(
function (always_disabled) {
if (always_disabled)
title = 'Enable gPass for ALL websites';
else
title = 'Disable gPass for ALL websites';
browser.menus.update("always_disable",
{
"title":title
}
);
browser.menus.refresh();
}
);
}
);
}
}
async function self_test()
{
mkey = crypto_pbkdf2("password", "salt", 4096);
res = await encrypt_ecb(mkey, "DDDDDDDDDDDDDDDD");
reference = new Uint8Array([0xc4, 0x76, 0x01, 0x07, 0xa1, 0xc0, 0x2f, 0x22, 0xee, 0xbe, 0x60,
0xff, 0x65, 0x33, 0x5b, 0x9e]);
if (res != ab2str(reference))
{
console.log("Self test ERROR !");
}
else
console.log("Self test OK !");
}
//self_test();
extension_load();
+9 -37
View File
@@ -1,29 +1,23 @@
/*
Copyright (C) 2013-2022 Grégory Soutadé
Copyright (C) 2013-2017 Grégory Soutadé
This file is part of gPass.
gPass is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gPass is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
var default_preferences = {"pbkdf2_level": 1000,
"account_url": "https://gpass-demo.soutade.fr/demo",
"always_disabled":false,
"popup_clipboard":false
};
export function get_preference(key)
function getPref(key)
{
// Inspired from https://github.com/akiomik/chrome-storage-promise/
var promise = new Promise((resolve, reject) => {
@@ -38,36 +32,14 @@ export function get_preference(key)
})
.then(function (pref) {
if (!pref.hasOwnProperty(key))
{
if (default_preferences.hasOwnProperty(key))
return default_preferences[key];
else
return null;
}
return default_preferences[key];
return pref[key];
});
return promise;
}
export function set_preference(key, value, sendResponse)
function setPref(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);
});
chrome.storage.local.set({key:value}, function ok() {});
}
export function delete_preference(key)
{
chrome.storage.local.remove(key);
}
export function send_tab_message(tab_id, parameters, callback)
{
chrome.tabs.sendMessage(tab_id, parameters, {}, callback);
}
+359 -298
View File
@@ -1,28 +1,47 @@
/*
Copyright (C) 2013-2022 Grégory Soutadé
Copyright (C) 2013-2017 Grégory Soutadé
This file is part of gPass.
gPass is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gPass is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
/* 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};
var protocol_version = 4;
var account_url = null;
var crypto_v2_logins_size = 0;
SERVER = {OK : 0, FAILED : 1, RESTART_REQUEST : 2};
// http://stackoverflow.com/questions/3745666/how-to-convert-from-hex-to-ascii-in-javascript
function hex2a(hex) {
var str = '';
for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}
function a2hex(_str_) {
var hex = '';
for (var i = 0; i < _str_.length; i++)
{
var c = _str_.charCodeAt(i).toString(16);
if (c.length == 1) c = "0" + c;
hex += c;
}
return hex;
}
function debug(s)
{
@@ -30,13 +49,260 @@ function debug(s)
console.log(s);
}
function notify(text, data)
async function generate_request(domain, login, mkey, iv, old)
{
browser.runtime.sendMessage({type: "notification", options:{"message":text, "data":data}});
if (old)
{
var v = "@@" + domain + ";" + login;
debug("will encrypt " + v);
enc = encrypt_ecb(mkey, v);
}
else
{
var v = domain + ";" + login;
debug("will encrypt " + v);
while ((v.length % 16))
v += "\0";
hash = await digest(v);
v += hash.slice(8, 24);
enc = encrypt_cbc(mkey, iv, v);
}
return enc;
}
async function ask_server(form, field, logins, domain, wdomain, mkey, submit)
{
account_url = await getPref("account_url");
var gpass_enabled = true;
var salt = parseURI.parseUri(account_url);
salt = salt["host"] + salt["path"];
debug("salt " + salt);
pbkdf2_level = await getPref("pbkdf2_level");
global_iv = await simple_pbkdf2(salt, mkey, pbkdf2_level);
global_iv = global_iv.slice(0, 16);
mkey = crypto_pbkdf2(mkey, salt, pbkdf2_level);
debug("global_iv " + a2hex(global_iv));
keys = "";
for(key_index=0, a=0; a<logins.length; a++, key_index++)
{
enc = await generate_request(domain, logins[a], mkey, global_iv, 0);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + key_index + "=" + a2hex(enc);
if (wdomain != "")
{
enc = await generate_request(wdomain, logins[a], mkey, global_iv, 0);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + (++key_index) + "=" + a2hex(enc);
}
}
crypto_v2_logins_size = key_index;
if (await getPref("crypto_v1_compatible"))
{
for(a=0; a<logins.length; a++, key_index++)
{
enc = await generate_request(domain, logins[a], mkey, global_iv, 1);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + key_index + "=" + a2hex(enc);
if (wdomain != "")
{
enc = await generate_request(wdomain, logins[a], mkey, global_iv, 1);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + (++key_index) + "=" + a2hex(enc);
}
}
}
debug("Keys " + keys);
var gPassRequest = new XMLHttpRequest();
var ret = SERVER.OK;
// gPassRequest.addEventListener("progress", function(evt) { ; }, false);
gPassRequest.addEventListener("load", async function(evt) {
var ciphered_password = "";
var server_pbkdf2_level = 0;
var server_version = 0;
var matched_key = 0;
var r = this.responseText.split("\n");
debug("resp " + r);