101 Commits
v0.2 ... v0.8.2

Author SHA1 Message Date
09c8d6bcba Merge branch 'master' of soutade.fr:gpass into HEAD 2018-06-30 09:08:39 +02:00
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
81385fbf88 Update ChangeLog 2017-12-05 21:02:50 +01:00
4cb4b44690 Merge branch 'master' of soutade.fr:gpass 2017-12-05 18:34:23 +01:00
2f12a09a1b Update ChangeLog 2017-12-05 18:33:43 +01:00
Gregory Soutade
b216ec9928 Add Privacy Policy information 2017-12-05 08:06:48 +01:00
Gregory Soutade
da4a99a6ef Scroll to page bottom when user adds a new password 2017-12-05 08:05:47 +01:00
f98cc4a863 Do simpler things to create wildcard domains 2017-09-14 20:20:42 +02:00
5a1d6c7390 Update README with full information and add a ChangeLog 2017-07-23 09:40:29 +02:00
c511e49725 Server: update random password generation and add a "simple password" button 2017-07-19 19:22:49 +02:00
8469c01b13 Fix minor error : crypto_v2_logins_size must be always updated 2017-07-19 19:22:49 +02:00
7162129220 Put firefox background.js has persistent 2017-07-19 19:22:49 +02:00
017bda025e Some comment and copyright updates 2017-07-19 19:22:49 +02:00
39582e0f26 CLI: Use printf return to compute string size instead of hardcoded values 2017-07-19 19:22:49 +02:00
86e926e268 Deactivate DEBUG mode 2017-07-18 21:00:02 +02:00
e341963675 Block URL request if masterkey is present in parameters 2017-07-08 08:43:26 +02:00
7a7d2fd724 Try to raise onChange event and "enter" key before submit form 2017-07-08 08:42:34 +02:00
65821a4e47 Index for new crypto scheme was badly computed in webextension 2017-07-08 08:39:22 +02:00
0caa8a66e1 Handle Ctrl+C and Ctrl+Z in gpass_cli 2017-07-08 08:37:16 +02:00
Gregory Soutade
35f49d24b3 CLI: Clear displayed password after 30 seconds (if found) 2017-05-06 18:13:36 +02:00
09e0d85d97 Merge branch 'master' of soutade.fr:gpass 2017-04-17 20:42:08 +02:00
416a4d9581 Use old passwords decryption for protocols < 4 2017-04-17 20:39:53 +02:00
6dfcab813d Update options.js of firefox webextension 2017-04-17 20:39:53 +02:00
da72cb46eb Add new encryption scheme in CLI. Fix a bug : encrypt_domain doesn't add \0 on message returns leading to malformed server requests 2017-04-17 20:39:53 +02:00
36db5056a3 Add new encryption scheme in chrome addon/firefox webextension 2017-04-17 20:39:53 +02:00
c3fab882f2 Add compat.js for getPref and setPref 2017-04-17 20:39:53 +02:00
74b1010881 Add icons to icon path when displaying notifications 2017-04-17 20:39:53 +02:00
562a84d984 Rename compat.js in misc.js and remove old crypto libraries 2017-04-17 20:39:53 +02:00
7800eadfaa Move icons into a single directory 2017-04-17 20:39:53 +02:00
c64f886188 Add Firefox WebExtension 2017-04-17 20:39:53 +02:00
e759c13d64 First merge with Chrome et Firefox web extension (not tested in chrome) 2017-04-17 20:39:53 +02:00
1d71ca6861 Change protocol version (3 -> 4) in CLI : PKDBF2 is renamed in PBKDF2 2017-04-17 20:39:53 +02:00
65ca3a3d3d Change protocol version (3 -> 4) : PKDBF2 is renamed in PBKDF2. This also avoid mismatch with new encryption system 2017-04-17 20:39:53 +02:00
136920404d Add (QUnit) server tests 2017-04-17 20:37:26 +02:00
ef82f2640f Fix a bug : update_masterkey generates a token from only one master key and not for the corresponding masterkey 2017-04-17 20:37:26 +02:00
65f8f0f21e Fix a bug : shadow logins passwords were not deleted 2017-04-17 20:37:26 +02:00
a180cb62d7 Use a variable (server_url) instead of document.documentURI 2017-04-17 20:37:26 +02:00
b4b54ec57c Checks for right shadow login when adding an entry
Use exec() instead of query() when it's necessary for SQL queries
2017-04-17 20:37:26 +02:00
48571b31c1 Set shadow logins ON by default 2017-04-17 20:37:26 +02:00
bc2a510b8d Automatically come back to top when timeout is raised and clear keys are cleaned 2017-04-17 20:37:26 +02:00
82cef032eb Reduce scroll top arrow 2017-04-17 20:37:26 +02:00
d48d1e94a9 Add scrollToTop javascript function 2017-04-17 20:37:26 +02:00
cef1194ad0 Add new encryption scheme in server part. 2017-04-17 20:37:26 +02:00
89465f4c68 Don't use shadow if the flag is not defined 2017-04-17 20:37:26 +02:00
6fa296ebc4 Use native crypto functions from misc.js 2017-04-17 20:37:26 +02:00
241867e93c Database access optimization if there is no shadow logins 2017-04-17 20:37:26 +02:00
6d2e202aff Create a password is now on top of web page. Remove old javascript libraries. 2017-04-17 20:37:26 +02:00
32fd8355e1 New protocol (fix mispelled PKBDF2) 2017-04-17 20:37:26 +02:00
Gregory Soutade
527fc52539 Forgot startClearTimer() when adding a password 2016-09-25 20:35:11 +02:00
29e2c8337c Clear master keys and reset passwords after 15 minutes of inactivity 2016-08-20 13:23:36 +02:00
9b9c36070b Fix a bug in gpass_cli : sprintf needs final 0 while we must not add it for encryption 2016-08-20 10:47:54 +02:00
0cc706d260 Add command line interface (cli) 2016-08-14 11:37:39 +02:00
89ccd0575f Forgot to update reference database 2016-05-21 09:58:30 +02:00
Gregory Soutade
ba75b73503 Merge branch 'master' of soutade.fr:gpass 2015-12-04 17:03:16 +01:00
Gregory Soutade
487283626f Remove \ character from password generation to avoid confusions 2015-12-04 17:03:12 +01:00
Gregory Soutade
6604fbb6e1 Add two new protections : REQUESTS_MIN_DELAY and MAX_PASSWORDS_PER_REQUEST (see conf.php) 2015-12-04 17:02:31 +01:00
Gregory Soutade
cc66b612ef New version of database, add last_access_time field 2015-12-04 17:01:41 +01:00
81b6116a9f Update firefox addon to be compliant with new jpm building tool 2015-11-05 18:42:15 +01:00
075dd914f1 Give a chance to fields with type other than text and email to be a username (improve website compatibility) 2015-11-05 18:16:33 +01:00
Gregory Soutade
1d22f425e9 Add export function 2015-09-17 20:32:29 +02:00
Gregory Soutade
b255a8cd90 Display an error message when a query failed (previous : silent fail...) 2015-09-05 09:21:45 +02:00
Gregory Soutade
b330af258f Move code to encrypt into PasswordEntry function
Add Update Masterkey feature
2015-04-23 21:36:50 +02:00
root
e16fa0c218 Remove default parameters (Chrome...) 2015-03-27 18:23:26 +01:00
3108195ce3 Set default address in HTTPS for chrome addon 2015-02-09 19:18:27 +01:00
Gregory Soutade
3f0636976e Merge branch 'master' of soutade.fr:gpass 2015-02-09 18:58:08 +01:00
Gregory Soutade
e9c6208b54 Introduce shadow logins 2015-02-09 18:57:49 +01:00
f88a0d1f29 Mistake in pkdbf2 requires, it's hmac and not hmac.js 2015-02-05 20:07:11 +01:00
636d403396 Add short name to chrome addon manifest 2015-02-03 20:11:30 +01:00
423799bfd7 Integrate icons in addons 2015-01-29 21:19:29 +01:00
6a86ab989c Add 16, 32, 64 and 128 px icons 2015-01-29 21:19:00 +01:00
601ddfc629 Little mistake in firefox addon notify() 2015-01-27 21:23:03 +01:00
d395807c98 Add Chrome addon 2015-01-27 21:10:55 +01:00
2757b81e64 Rename resSources directories into resources 2015-01-27 21:10:09 +01:00
9dc74cb942 Add gpass icon 2015-01-27 21:08:46 +01:00
714f0f570f Update version to 0.6 2015-01-27 21:08:01 +01:00
5cfb47c470 Use wrappers for get and set prefs
Fix some syntax issues in hmac.js
2015-01-22 21:50:07 +01:00
e0f8670a58 Add var before local variables 2015-01-21 18:56:50 +01:00
035576998d Move hmac code into hmac.js file 2015-01-21 18:56:32 +01:00
a5b3dec5b0 Move notifications into notify function 2015-01-21 18:49:26 +01:00
f5a0da3f69 Add a message when password is replaced but not submitted (@_ function) 2014-08-26 18:56:20 +02:00
Gregory Soutade
f8472d7ca4 Merge branch 'master' of soutade.fr:gpass 2014-06-10 20:41:06 +02:00
Gregory Soutade
de0a443ebb Add a favicon 2014-06-10 20:40:53 +02:00
53db16b832 Update README.md 2014-06-10 19:19:48 +02:00
b0ea92b71b Add "@_" prefix support to replace password without submit 2014-06-10 19:04:56 +02:00
Gregory Soutade
ee75e0ea06 Automatically change user when its selected (server side) 2014-04-02 07:47:54 +02:00
Gregory Soutade
6d30db3ca0 Merge branch 'master' of soutade.fr:gpass 2014-03-07 17:36:24 +01:00
Gregory Soutade
fceab22f48 Server : Disable warning on empty master key (clear passwords from others) 2014-03-07 17:36:12 +01:00
eba9ba35eb Merge branch 'master' of soutade.fr:gpass 2014-02-27 18:47:01 +01:00
cbeb7e3d73 Don't even block submit function with synchronous XmlHttpRequest
Enable wildcard for one level domains
2014-02-27 18:35:25 +01:00
Gregory Soutade
ccaca8c598 Don't warn on user change 2014-02-19 17:37:27 +01:00
Gregory Soutade
ce1d010b85 Warn when no password are unciphered using a masterkey
Clear masterkey after "See" or "Add" action
2014-02-19 17:34:51 +01:00
Gregory Soutade
6f8f952a92 Master key is now in clear text (not password) when you add a new password. This reduce typo errors 2014-02-17 08:00:28 +01:00
Gregory Soutade
83e1291ae7 Add pwdmeter.js from Jeff Todnem (https://www.todnem.com/) to have a feedback on master key strength 2014-02-01 10:50:23 +01:00
Gregory Soutade
61828b741d Remove licence from non PHP code in server 2014-01-23 07:44:07 +01:00
a83354e592 Update README 2014-01-22 17:42:30 +01:00
8eee0060f6 Add Minimum PKDFB2 level check in firefox_addon 2014-01-21 20:31:29 +01:00
84eaf0c6a1 New protocol v3 : include pkdbf2 level
Remove hashtable from firefox addon
Rework firefox addon
Add pkdbf2_level as a preference (hidden)
2014-01-21 19:00:26 +01:00
1ff4a87beb Fix a little regression + deactivate debug 2014-01-12 10:43:02 +01:00
8a22db95dc Now addon automatically sends wildcard domain in addition to current domain to increase website compatibility 2014-01-12 10:27:56 +01:00
Gregory Soutade
71b17a1ff0 index.php now includes ../../_user to simulate a symbolic link (for easier updates)
Fix default parameters in gpass.js (not compatible with chrome and IE)
2014-01-02 09:56:17 +01:00
68 changed files with 5123 additions and 2077 deletions

68
ChangeLog Normal file
View File

@@ -0,0 +1,68 @@
**v0.8.2 :**
Server
Addon
* Error in wildcard domain generation
* Forgot some console.log in code (replace it with debug function)
CLI
**v0.8.1 :**
Server
* Scroll to page bottom when user adds a new password
Addon
* Add Privacy Policy information
* Run a simpler algorithm for wildcard domains
CLI
* Run a simpler algorithm for wildcard domains
**v0.8 :**
Server
* Clear master keys and reset passwords after 15 minutes of inactivity
* Set USE_SHADOW_LOGINS by default
* New crypto scheme (Use CBC chaining and fix a security problem with salt) and protocol v4. not backward compatible with v3
* Add QUnit tests
* New password form is now on top of the page
* Add a button to go to the top of the page when scrolling
* Add simple password button
* Rework password generation for most user friendly passwords (less special characters, more letters)
Addon
* New webextension for Firefox is provided. It shares most of code with Chrome extension and use native crypto API
* Block connection when masterkey is sent in clear (password replacement failed). **Doesn't work with Firefox**
CLI
* Add command line interface (CLI)
**v0.7 :**
Server
* Fix a bug for Chrome browser (doesn't support default parameters)
* Display an error message when a query fails
* You can now export clear password database (only unciphered passwords)
* New database version : 2
* Add two new protections : REQUESTS_MIN_DELAY and MAX_PASSWORDS_PER_REQUEST (see conf.php)
* Remove '\' character from password generation
Addon
* Addon is now compatible with more websites
* Use jpm building tool instead of cfx for Firefox Addon
**v0.6 : **
Addon
* Add support for "@_masterkey" input

47
PrivacyPolicy.md Normal file
View File

@@ -0,0 +1,47 @@
gPass web browser extension Privacy Policy
------------------------------------------
## Information we collect ##
The gPass extension collect three information once invoked :
* Site address URL
* Login name
* Master key
## How we use information we collect ##
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.
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.
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 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.
## Information security ##
Information transmitted to the server are done through an HTTPS AJAX request.
Data are crypted using AES 256 CBC algorithm and the master key is prior
derived using PKBDF2 algorithm.

7
README
View File

@@ -1,7 +0,0 @@
gPass : global Password.
Copyright (C) 2013 Grégory Soutadé
Licence : GPL v3
See http://indefero.soutade.fr/p/gpass/ for further information

91
README.md Normal file
View File

@@ -0,0 +1,91 @@
gPass : global Password for Firefox and Chrome
==============================================
Introduction
------------
Everyday we have a lot of passwords to manage corresponding to a lot of accounts we use. It's hard to remain all of these, moreover if we don't use it often. So, what most people do is to generate only a subset of passwords easy to remain. This implies two common errors :
* 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/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 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 !
Usage
-----
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. 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 columns in database are "login" and "password".
login is compounded by "domain;login" is salted and encrypted with AES 256-CBC
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
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 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 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 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 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
----------------------
A command line interface is also available with the following usage :
Usage: ./gpass_cli [-f config_file] [-p server_port] [-c CA_certificate_path] [-l PBKDF2_level] [-s gpass_server] [-v] -d domain -u username
You can save recurrent parameters into a configuration file. Default config file is found at $HOME/.local/share/gpass/gpass.ini
The dependencies are libcurl and OpenSSL (-dev packages : ie _libcurl4-openssl-dev_ and _libssl-dev_)
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](http://indefero.soutade.fr/p/gpass).

View File

@@ -0,0 +1,95 @@
/*
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/>.
*/
function url_block_callback(details)
{
// console.log(JSON.stringify(details));
if (details.requestBody)
{
if (details.requestBody.formData)
{
for (var key in details.requestBody.formData)
{
for(var idx in details.requestBody.formData[key])
{
value = details.requestBody.formData[key][idx];
if (value.startsWith("@@") ||
value.startsWith("@_"))
return {cancel: true};
}
}
}
/*
// Analyse POST parameters
if (details.method == "POST" && details.requestBody.raw)
{
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};
}
*/
}
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"]);
}
});
}
});

45
chrome_addon/compat.js Normal file
View File

@@ -0,0 +1,45 @@
/*
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/>.
*/
function getPref(key)
{
// Inspired from https://github.com/akiomik/chrome-storage-promise/
var promise = new Promise((resolve, reject) => {
chrome.storage.local.get(key, (items) => {
let err = chrome.runtime.lastError;
if (err) {
reject(err);
} else {
resolve(items);
}
});
})
.then(function (pref) {
if (!pref.hasOwnProperty(key))
return default_preferences[key];
return pref[key];
});
return promise;
}
function setPref(key, value)
{
chrome.storage.local.set({key:value}, function ok() {});
}

1
chrome_addon/icons Symbolic link
View File

@@ -0,0 +1 @@
../resources/icons/

467
chrome_addon/lib/main.js Normal file
View File

@@ -0,0 +1,467 @@
/*
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 DEBUG = false;
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)
{
if (DEBUG)
console.log(s);
}
async function generate_request(domain, login, mkey, iv, old)
{
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 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);
for(var a=0; a<r.length; a++)
{
debug("Analyse " + r[a]);
params = r[a].split("=");
if (params.length != 2 && params[0] != "<end>")
{
notify("Error : It seems that it's not a gPass server",
this.responseText);
ret = SERVER.FAILED;
break;
}
switch(params[0])
{
case "protocol":
debug("protocol : " + params[1]);
if (params[1].indexOf("gpass-") != 0)
{
notify("Error : It seems that it's not a gPass server",
this.responseText);
ret = SERVER.FAILED;
break;
}
server_protocol_version = params[1].match(/\d+/)[0];
if (server_protocol_version > protocol_version)
{
notify("Protocol version not supported, please upgrade your addon",
"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;
setPref("pbkdf2_level", pbkdf2_level);
ret = SERVER.RESTART_REQUEST;
}
break;
case "<end>":
break;
default:
debug("Unknown command " + params[0]);
notify("Error : It seems that it's not a gPass server",
this.responseText);
ret = SERVER.FAILED;
break;
}
}
if (ret != SERVER.OK)
{
return;
}
if (ciphered_password != "")
{
debug("Ciphered password : " + ciphered_password);
if (matched_key >= crypto_v2_logins_size)
// Crypto v1
{
clear_password = await decrypt_ecb(mkey, hex2a(ciphered_password));
// Remove trailing \0 and salt
clear_password = clear_password.replace(/\0*$/, "");
clear_password = clear_password.substr(0, clear_password.length-3);
}
else
{
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);
field.value = clear_password;
// Remove gPass event listener and submit again with clear password
if (submit)
{
form.removeEventListener("submit", on_sumbit, true);
// Propagate change
change_cb = field.onchange;
if (change_cb)
change_cb();
// Try to type "enter"
var evt = new KeyboardEvent("keydown");
delete evt.which;
evt.which = 13;
field.dispatchEvent(evt);
// Submit form
form.submit();
}
else
{
notify("Password successfully replaced",
"Password successfully replaced");
}
}
else
{
debug("No password found");
ret = SERVER.FAILED;
notify("No password found in database",
"No password found in database");
}
}, false);
gPassRequest.addEventListener("error", function(evt) {
debug("error");
ret = false;
notify("Error",
"Error");
}, false);
debug("connect to " + account_url);
gPassRequest.open("POST", account_url, true);
gPassRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
gPassRequest.send(keys);
return ret;
}
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++)
res += parts[i] + ".";
// Remove last "."
return res.substr(0, res.length-1);
}
// Simple xxx.com
else if (parts.length == 2)
return "*." + domain;
return "";
}
function _add_name(logins, name)
{
for(var i=0; i<logins.length; i++)
if (logins[i] == name) return ;
logins.push(name);
}
function try_get_name(fields, type_filters, match)
{
var user = null;
var all_logins = new Array();
for (var i=0; i<fields.length; i++)
{
var field = fields[i];
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;
_add_name(all_logins, field.value);
}
}
}
}
if (user != null)
return new Array(user);
else
return all_logins;
}
function on_sumbit(e)
{
var form = this;
var fields = form.getElementsByTagName("input");
var domain = parseURI.parseUri(form.ownerDocument.baseURI);
domain = domain["host"];
var wdomain = wildcard_domain(domain);
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
if (!logins.length)
{
type_filters.push("password");
logins = try_get_name(fields, type_filters, false);
}
// Look for <input type="password" value="@@...">
for (var i=0; i<fields.length; i++)
{
var field = fields[i];
if (field.getAttribute("type") == "password")
{
debug(field.value);
password = field.value;
if (password.indexOf("@@") != 0 && password.indexOf("@_") != 0)
continue;
// Remove current value to limit master key stealing
field.value = "";
mkey = password.substring(2);
e.preventDefault();
var ret = ask_server(form, field, logins, domain, wdomain, mkey, (password.indexOf("@@") == 0));
ret.then(function(ret){
switch(ret)
{
case SERVER.OK:
break;
case SERVER.FAILED:
if (logins !== all_logins)
{
ask_server(form, field, all_logins, domain, wdomain, mkey, (password.indexOf("@@") == 0));
}
break;
case SERVER.RESTART_REQUEST:
i = -1; // Restart loop
break;
}
});
}
}
return false;
}
function document_loaded(doc)
{
var has_login_form = false;
// If there is a password in the form, add a "submit" listener
for(var i=0; i<doc.forms.length; i++)
{
var form = doc.forms[i];
var fields = form.getElementsByTagName("input");
for (a=0; a<fields.length; a++)
{
var field = fields[a];
if (field.getAttribute("type") == "password")
{
block_url(form.action);
old_cb = form.onsubmit;
if (old_cb)
form.removeEventListener("submit", old_cb);
form.addEventListener("submit", on_sumbit);
if (old_cb)
form.addEventListener("submit", old_cb);
has_login_form = true;
break;
}
}
}
/* Request can be sent to another URL... */
if (has_login_form)
block_url("<all_urls>");
}
document_loaded(document);
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 !");
}
console.log("Welcome to gPass web extension v0.8.2 !");
console.log("Privacy Policy can be found at http://indefero.soutade.fr/p/gpass/source/tree/master/PrivacyPolicy.md");
console.log("");
//self_test();

240
chrome_addon/lib/misc.js Normal file
View File

@@ -0,0 +1,240 @@
/*
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",
"crypto_v1_compatible": true};
var browser = browser || chrome;
var crypto = crypto || window.crypto;
function notify(text, data)
{
browser.runtime.sendMessage({type: "notification", options:{"message":text}});
}
function block_url(url)
{
debug("Block URL " + url);
browser.runtime.sendMessage({type: "block_url", options:{"url":url}});
}
// https://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
function str2ab2(str) {
var chars = []
for (var i=0, strLen=str.length; i < strLen; i++) {
chars.push(str.charCodeAt(i));
}
return new Uint8Array(chars);
}
function str2ab(str) {
var buf = new ArrayBuffer(str.length);
// var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
var bufView = new Uint8Array(buf);
for (var i=0, strLen=str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return bufView;
}
function crypto_pbkdf2(mkey, salt, level)
{
AESCBC = {
name: "AES-CBC",
length: 256,
}
var key = str2ab(mkey);
return crypto.subtle.importKey("raw", key, {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"])
.then(function(key){
//sha-256
return crypto.subtle.deriveKey({
name: "PBKDF2",
salt: str2ab(salt),
iterations: level,
hash: "SHA-256",
}, key, AESCBC, false, ["encrypt", "decrypt", "unwrapKey", "wrapKey"])
.then(function(key) {
return key;
})
.catch(function(err){
debug("Error derive key " + err);
});
})
.catch(function(err) {
debug("Error import key" + err);
});
}
function simple_pbkdf2(mkey, salt, level)
{
AESCBC = {
name: "AES-CBC",
length: 256,
}
var key = str2ab(mkey);
return crypto.subtle.importKey("raw", key, {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"])
.then(function(key){
//sha-256
return crypto.subtle.deriveKey({
name: "PBKDF2",
salt: str2ab(salt),
iterations: level,
hash: "SHA-256",
}, key, AESCBC, true, ["unwrapKey", "wrapKey"])
.then(function(key) {
return crypto.subtle.exportKey("raw", key)
.then(function (key) {
return ab2str(key);
});
})
.catch(function(err){
debug("Error derive key " + err);
});
})
.catch(function(err) {
debug("Error import key" + err);
});
}
function _encrypt(mkey, iv, data)
{
while ((data.length % 16))
data += "\0";
data = str2ab(data);
promise = mkey.then(function(mkey){
return crypto.subtle.encrypt({
name: "AES-CBC",
iv: iv
}, mkey, data)})
.then(function(encrypted) {
return ab2str(encrypted);
})
.catch(function(encryption) {
debug("Encryption rejected " + encryption);
});
return promise;
}
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]);
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){
return crypto.subtle.decrypt({
name: "AES-CBC",
iv: iv
}, mkey, data)})
.then(function(decrypted) {
return ab2str(decrypted);
})
.catch(function(decryption) {
debug("Decryption rejected " + decryption);
});
return promise;
}
async function encrypt_ecb(mkey, data)
{
var result = "";
debug("Encrypt ECB " + data);
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
while (data.length > 16)
{
res = await _encrypt(mkey, nulliv, data.slice(0, 16));
// Remove PKCS7 padding
result += res.slice(0, 16);
data = data.slice(16);
}
res = await _encrypt(mkey, nulliv, data);
result += res.slice(0, 16);
return result;
}
async function decrypt_ecb(mkey, data)
{
var result = "";
debug("Decrypt ECB " + data);
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
while (data.length > 16)
{
res = await _decrypt(mkey, nulliv, data.slice(0, 16));
// Remove PKCS7 padding
result += res.slice(0, 16);
data = data.slice(16);
}
res = await _decrypt(mkey, nulliv, data);
result += res.slice(0, 16);
return result;
}
async function encrypt_cbc(mkey, iv, data)
{
debug("Encrypt CBC " + data);
var result = await _encrypt(mkey, str2ab(iv), data);
// Remove PKCS7 padding
return result.slice(0, result.length-16);
}
async function decrypt_cbc(mkey, iv, data)
{
debug("Decrypt CBC " + data);
var result = await _decrypt(mkey, str2ab(iv), data);
// Remove PKCS7 padding
return result.slice(0, result.length-16);
}
async function digest(data)
{
return crypto.subtle.digest("SHA-256", str2ab(data)).then(function (hash) {
return ab2str(hash);
});
}

View File

@@ -0,0 +1,32 @@
// parseUri 1.2.2
// (c) Steven Levithan <stevenlevithan.com>
// MIT License
parseURI = {
parseUri : function (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
},
parser: {
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
}},
m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
uri = {},
i = 14;
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;
});
return uri;
}
};

View File

@@ -0,0 +1,36 @@
{
"manifest_version": 2,
"name": "gPass",
"short_name": "gPass",
"version": "0.8.2",
"description": "gPass : global password manager",
"icons" : {"16":"icons/gpass_icon_16.png", "32":"icons/gpass_icon_32.png", "64":"icons/gpass_icon_64.png", "128":"icons/gpass_icon_128.png"},
"author" : "Grégory Soutadé",
"homepage_url" : "http://indefero.soutade.fr/p/gpass",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["lib/parseuri.js", "lib/misc.js", "compat.js", "lib/main.js"],
"run_at" : "document_idle",
"all_frames" : true
}
],
"background": {
"persistent": true,
"scripts": ["background.js"]
},
"options_page": "options.html",
"permissions": [
"<all_urls>",
"notifications",
"webRequest",
"webRequestBlocking",
"tabs",
"storage"
]
}

21
chrome_addon/options.html Normal file
View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>gPass</title>
</head>
<body>
<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 />
<br/>
<br/>
<b>Crypto v1 compatible </b> Compatible with old crypto schema (AES ECB). Use it for encrypted passwords with server <= 0.7 <input id="crypto_v1_compatible" type="checkbox"/><br />
<br/>
<input type="button" id="save" value="Save"/>
<script type="text/javascript" src="options.js">
</script>
</body>
</html>

40
chrome_addon/options.js Normal file
View File

@@ -0,0 +1,40 @@
var default_preferences = {"pbkdf2_level": 1000,
"account_url": "https://gpass-demo.soutade.fr/demo",
"crypto_v1_compatible": true};
function save() {
var account_url = document.getElementById('account_url').value;
var pbkdf2 = document.getElementById('pbkdf2').value;
var crypto_v1_compatible = document.getElementById('crypto_v1_compatible').checked;
chrome.storage.local.set({
'account_url': account_url,
'pbkdf2': pbkdf2,
'crypto_v1_compatible': crypto_v1_compatible,
}, function() {
alert('Saved');
});
}
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'];
if (!prefs.hasOwnProperty("crypto_v1_compatible"))
crypto_v1_compatible = default_preferences['crypto_v1_compatible'];
else
crypto_v1_compatible = prefs['crypto_v1_compatible'];
document.getElementById('account_url').value = account_url;
document.getElementById('pbkdf2').value = pbkdf2;
document.getElementById('crypto_v1_compatible').checked = crypto_v1_compatible;
});
document.getElementById('save').addEventListener("click", save);

13
cli/Makefile Normal file
View File

@@ -0,0 +1,13 @@
CC=gcc
CFLAGS=-Wall -O2
LDFLAGS= -lcrypto -lcurl
TARGET=gpass_cli
SRCS=main.c ini.c
all: $(TARGET)
$(TARGET): $(SRCS)
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
clean:
rm -f $(TARGET) *.o *~

6
cli/gpass.ini.sample Normal file
View File

@@ -0,0 +1,6 @@
[params]
# ca_path=./ca_path/ca_authority.pem
# server=https://demo-gpass.soutade.fr/demo
# pbkdf2_level=1000
# server_port=443
# verify_ssl_peer=1

194
cli/ini.c Normal file
View File

@@ -0,0 +1,194 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "ini.h"
#if !INI_USE_STACK
#include <stdlib.h>
#endif
#define MAX_SECTION 50
#define MAX_NAME 50
/* Strip whitespace chars off end of given string, in place. Return s. */
static char* rstrip(char* s)
{
char* p = s + strlen(s);
while (p > s && isspace((unsigned char)(*--p)))
*p = '\0';
return s;
}
/* Return pointer to first non-whitespace char in given string. */
static char* lskip(const char* s)
{
while (*s && isspace((unsigned char)(*s)))
s++;
return (char*)s;
}
/* Return pointer to first char (of chars) or inline comment in given string,
or pointer to null at end of string if neither found. Inline comment must
be prefixed by a whitespace character to register as a comment. */
static char* find_chars_or_comment(const char* s, const char* chars)
{
#if INI_ALLOW_INLINE_COMMENTS
int was_space = 0;
while (*s && (!chars || !strchr(chars, *s)) &&
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
was_space = isspace((unsigned char)(*s));
s++;
}
#else
while (*s && (!chars || !strchr(chars, *s))) {
s++;
}
#endif
return (char*)s;
}
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
static char* strncpy0(char* dest, const char* src, size_t size)
{
strncpy(dest, src, size);
dest[size - 1] = '\0';
return dest;
}
/* See documentation in header file. */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user)
{
/* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
char line[INI_MAX_LINE];
#else
char* line;
#endif
char section[MAX_SECTION] = "";
char prev_name[MAX_NAME] = "";
char* start;
char* end;
char* name;
char* value;
int lineno = 0;
int error = 0;
#if !INI_USE_STACK
line = (char*)malloc(INI_MAX_LINE);
if (!line) {
return -2;
}
#endif
/* Scan through stream line by line */
while (reader(line, INI_MAX_LINE, stream) != NULL) {
lineno++;
start = line;
#if INI_ALLOW_BOM
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
(unsigned char)start[1] == 0xBB &&
(unsigned char)start[2] == 0xBF) {
start += 3;
}
#endif
start = lskip(rstrip(start));
if (*start == ';' || *start == '#') {
/* Per Python configparser, allow both ; and # comments at the
start of a line */
}
#if INI_ALLOW_MULTILINE
else if (*prev_name && *start && start > line) {
/* Non-blank line with leading whitespace, treat as continuation
of previous name's value (as per Python configparser). */
if (!handler(user, section, prev_name, start) && !error)
error = lineno;
}
#endif
else if (*start == '[') {
/* A "[section]" line */
end = find_chars_or_comment(start + 1, "]");
if (*end == ']') {
*end = '\0';
strncpy0(section, start + 1, sizeof(section));
*prev_name = '\0';
}
else if (!error) {
/* No ']' found on section line */
error = lineno;
}
}
else if (*start) {
/* Not a comment, must be a name[=:]value pair */
end = find_chars_or_comment(start, "=:");
if (*end == '=' || *end == ':') {
*end = '\0';
name = rstrip(start);
value = lskip(end + 1);
#if INI_ALLOW_INLINE_COMMENTS
end = find_chars_or_comment(value, NULL);
if (*end)
*end = '\0';
#endif
rstrip(value);
/* Valid name[=:]value pair found, call handler */
strncpy0(prev_name, name, sizeof(prev_name));
if (!handler(user, section, name, value) && !error)
error = lineno;
}
else if (!error) {
/* No '=' or ':' found on name[=:]value line */
error = lineno;
}
}
#if INI_STOP_ON_FIRST_ERROR
if (error)
break;
#endif
}
#if !INI_USE_STACK
free(line);
#endif
return error;
}
/* See documentation in header file. */
int ini_parse_file(FILE* file, ini_handler handler, void* user)
{
return ini_parse_stream((ini_reader)fgets, file, handler, user);
}
/* See documentation in header file. */
int ini_parse(const char* filename, ini_handler handler, void* user)
{
FILE* file;
int error;
file = fopen(filename, "r");
if (!file)
return -1;
error = ini_parse_file(file, handler, user);
fclose(file);
return error;
}

93
cli/ini.h Normal file
View File

@@ -0,0 +1,93 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifndef __INI_H__
#define __INI_H__
/* Make this header file easier to include in C++ code */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
/* Typedef for prototype of handler function. */
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value);
/* Typedef for prototype of fgets-style reader function. */
typedef char* (*ini_reader)(char* str, int num, void* stream);
/* Parse given INI-style file. May have [section]s, name=value pairs
(whitespace stripped), and comments starting with ';' (semicolon). Section
is "" if name=value pair parsed before any section heading. name:value
pairs are also supported as a concession to Python's configparser.
For each name=value pair parsed, call handler function with given user
pointer as well as section, name, and value (data only valid for duration
of handler call). Handler should return nonzero on success, zero on error.
Returns 0 on success, line number of first error on parse error (doesn't
stop on first error), -1 on file open error, or -2 on memory allocation
error (only when INI_USE_STACK is zero).
*/
int ini_parse(const char* filename, ini_handler handler, void* user);
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
close the file when it's finished -- the caller must do that. */
int ini_parse_file(FILE* file, ini_handler handler, void* user);
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
filename. Used for implementing custom or string-based I/O. */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user);
/* Nonzero to allow multi-line value parsing, in the style of Python's
configparser. If allowed, ini_parse() will call the handler with the same
name for each subsequent line parsed. */
#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif
/* Nonzero to allow inline comments (with valid inline comment characters
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
#define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
#define INI_INLINE_COMMENT_PREFIXES ";"
#endif
/* Nonzero to use stack, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif
/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif
/* Maximum line length for any line in INI file. */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 200
#endif
#ifdef __cplusplus
}
#endif
#endif /* __INI_H__ */

719
cli/main.c Normal file
View File

@@ -0,0 +1,719 @@
/*
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/>.
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <curl/curl.h>
#include <openssl/opensslv.h>
#include <openssl/evp.h>
#include "ini.h"
#define STRNCMP(a, b) strncmp(a, b, sizeof(b)-1)
#define DEFAULT_CONFIG_FILE ".local/share/gpass/gpass.ini"
#define DEFAULT_PBKDF2_LEVEL 1000
#define MASTER_KEY_LENGTH (256/8)
#define GLOBAL_IV_LENGTH 16
#define BLOCK_SIZE (128/8)
#define DEFAULT_SERVER_PORT 443
#define SERVER_PROTOCOL 4
#define RESPONSE_SIZE 2048
#define MAX_SUBDOMAINS 10
#define DISPLAY_TIME 30 // 30 seconds
struct gpass_parameters {
unsigned pbkdf2_level;
char *server;
char *salt;
char *domain;
char *username;
char *orig_master_key;
unsigned char *derived_master_key;
unsigned server_port;
unsigned verbose;
char *ca_path;
unsigned verify_ssl_peer;
unsigned port_set;
unsigned crypto_v1_compatible;
unsigned char *global_iv;
} ;
#if OPENSSL_VERSION_NUMBER >= 0x10010000
// OpenSSL >= 1.1
static EVP_MD_CTX * s_md_ctx;
#else
static EVP_MD_CTX * s_md_ctx;
static EVP_MD_CTX ss_md_ctx;
#define EVP_MD_CTX_new(...) &ss_md_ctx
#define EVP_MD_CTX_free(...)
#endif
static const EVP_MD * s_md_256;
static EVP_CIPHER_CTX * s_cipher_ctx;
static int s_stop_display = 0;
static void signal_handler(int signum)
{
s_stop_display = 1;
}
static void display_password(char* password, int time)
{
int print_len = 0;
for (; time && !s_stop_display; time--)
{
print_len = printf("\r(%02d) Password found: %s", time, password);
fflush(stdout);
sleep(1);
}
// Clear line
print_len++; // For C or Z
printf("\r");
while (print_len--)
printf(" ");
printf("\n");
}
static int digest(unsigned char** out, unsigned char* in, unsigned size)
{
*out = NULL;
EVP_DigestInit(s_md_ctx, s_md_256);
EVP_DigestUpdate(s_md_ctx, in, size);
*out = malloc(32);
return EVP_DigestFinal(s_md_ctx, *out, NULL);
}
static void derive_master_key(struct gpass_parameters* params)
{
if (!params->derived_master_key)
params->derived_master_key = malloc(MASTER_KEY_LENGTH);
if (!params->global_iv)
params->global_iv = malloc(GLOBAL_IV_LENGTH);
PKCS5_PBKDF2_HMAC(params->orig_master_key, strlen(params->orig_master_key),
(unsigned char*)params->salt, strlen(params->salt),
params->pbkdf2_level, EVP_sha256(),
MASTER_KEY_LENGTH, params->derived_master_key);
PKCS5_PBKDF2_HMAC(params->salt, strlen(params->salt),
(unsigned char*)params->orig_master_key, strlen(params->orig_master_key),
params->pbkdf2_level, EVP_sha256(),
GLOBAL_IV_LENGTH, params->global_iv);
}
static void bin_to_hex(unsigned char* bin, unsigned char* hex, unsigned bin_size)
{
unsigned char tmp;
for (; bin_size--; bin++)
{
tmp = (*bin >> 4) & 0xf;
if (tmp <= 9)
*hex++ = '0' + tmp;
else
*hex++ = 'a' + (tmp-10);
tmp = *bin & 0xf;
if (tmp <= 9)
*hex++ = '0' + tmp;
else
*hex++ = 'a' + (tmp-10);
}
}
static void hex_to_bin(unsigned char* bin, unsigned char* hex, long hex_size)
{
unsigned char tmp;
// Round to 2
hex_size &= ~1;
for (; hex_size; hex_size-=2, bin++)
{
tmp = *hex++;
if (tmp >= '0' && tmp <= '9')
*bin = (tmp - '0') << 4;
else if (tmp >= 'a' && tmp <= 'f')
*bin = ((tmp - 'a')+10) << 4;
else
*bin = ((tmp - 'A')+10) << 4;
tmp = *hex++;
if (tmp >= '0' && tmp <= '9')
*bin |= (tmp - '0');
else if (tmp >= 'a' && tmp <= 'f')
*bin |= ((tmp - 'a')+10);
else
*bin |= ((tmp - 'A')+10);
}
}
static void encrypt_domain_v1(struct gpass_parameters* params, char* domain,
unsigned char** res, unsigned* out_size)
{
unsigned size = 2+strlen(domain)+1+strlen(params->username);
unsigned char* buffer, *tmp;
if (params->verbose)
printf("%s: %s\n", __func__, domain);
if ((size % BLOCK_SIZE))
size = ((size/BLOCK_SIZE)+1)*BLOCK_SIZE;
buffer = malloc(size+1); // Cause snprintf() add a final \0
memset(buffer, 0, size+1);
snprintf((char*)buffer, size+1, "@@%s;%s", domain, params->username);
tmp = malloc(size);
*res = malloc(size*2);
EVP_EncryptInit(s_cipher_ctx, EVP_aes_256_ecb(), params->derived_master_key, NULL);
EVP_CipherUpdate(s_cipher_ctx, tmp, (int*)out_size, buffer, size);
bin_to_hex(tmp, *res, size);
*out_size *= 2;
free(buffer);
free(tmp);
}
static void encrypt_domain(struct gpass_parameters* params, char* domain,
unsigned char** res, unsigned* out_size)
{
unsigned size = strlen(domain)+1+strlen(params->username);
unsigned padded_size;
unsigned char* buffer, *tmp;
if (params->verbose)
printf("%s: %s\n", __func__, domain);
if ((size % BLOCK_SIZE))
size = ((size/BLOCK_SIZE)+1)*BLOCK_SIZE;
padded_size = size;
size += 16; // For digest
buffer = malloc(size);
memset(buffer, 0, size);
snprintf((char*)buffer, size, "%s;%s", domain, params->username);
// Append digest
digest(&tmp, buffer, padded_size);
memcpy(&buffer[padded_size], &tmp[8], 16);
free(tmp);
tmp = malloc(size);
*res = malloc(size*2);
EVP_EncryptInit(s_cipher_ctx, EVP_aes_256_cbc(), params->derived_master_key, params->global_iv);
EVP_CipherUpdate(s_cipher_ctx, tmp, (int*)out_size, buffer, size);
bin_to_hex(tmp, *res, size);
*out_size *= 2;
free(buffer);
free(tmp);
}
static void append_to_request(char** request, char* new_req, unsigned new_req_size)
{
static int cur_req_idx = 0;
int size_added;
if (!cur_req_idx)
{
*request = malloc(3+new_req_size+1);
snprintf(*request, 3+new_req_size+1, "k0=%s", new_req);
}
else
{
size_added = 4+new_req_size;
if (cur_req_idx >= 10)
size_added++;
*request = realloc(*request, strlen(*request)+1+size_added);
snprintf(&((*request)[strlen(*request)]), size_added+1, "&k%d=%s",
cur_req_idx, new_req);
}
cur_req_idx++;
}
static char* wildcard_domain(char* domain)
{
int cur_level = 1;
char* level_ptr[MAX_SUBDOMAINS], *tmp, *res = NULL;
int level_length[MAX_SUBDOMAINS];
memset(level_ptr, 0, sizeof(level_ptr));
memset(level_length, 0, sizeof(level_length));
level_ptr[0] = domain;
for (tmp=domain; *tmp && cur_level < MAX_SUBDOMAINS; tmp++)
{
if (*tmp == '.')
{
level_ptr[cur_level] = tmp+1;
level_length[cur_level-1] = tmp - level_ptr[cur_level-1];
cur_level++;
}
}
// Too much levels
if (cur_level >= MAX_SUBDOMAINS)
{
fprintf(stderr, "Error: Too much levels for domain %s\n", domain);
return NULL;
}
// Final level
level_length[cur_level-1] = tmp - level_ptr[cur_level-1];
tmp = NULL;
if (cur_level > 2)
{
// Standard root domain (zzz.xxx.com) or more
tmp = level_ptr[1];
}
// Simple xxx.com
else if (cur_level == 2)
tmp = level_ptr[0];
if (tmp)
{
res = malloc(2+strlen(tmp)+1);
sprintf(res, "*.%s", tmp);
}
return res;
}
static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
if ((size*nmemb) > RESPONSE_SIZE)
{
fprintf(stderr, "Error curl response is too big (%d bytes, max %d bytes)\n",
(int)(size*nmemb), RESPONSE_SIZE);
}
else
memcpy(userdata, ptr, size*nmemb);
return size*nmemb;
}
static int ask_server(struct gpass_parameters* params)
{
char* wc_domain, *saveptr, *token, *cur_ptr;
unsigned char* enc_domain;
unsigned enc_size, matched_key = 0, crypto_v1_index = 1;
char* request = NULL;
int ret = -1, res, len;
CURL *curl;
char response[RESPONSE_SIZE];
unsigned char password[256];
if (params->verbose)
printf("Username: %s\n", params->username);
encrypt_domain(params, params->domain, &enc_domain, &enc_size);
append_to_request(&request, (char*)enc_domain, enc_size);
free(enc_domain);
wc_domain = wildcard_domain(params->domain);
if (wc_domain)
{
crypto_v1_index++;
encrypt_domain(params, wc_domain, &enc_domain, &enc_size);
append_to_request(&request, (char*)enc_domain, enc_size);
free(enc_domain);
}
if (params->crypto_v1_compatible)
{
encrypt_domain_v1(params, params->domain, &enc_domain, &enc_size);
append_to_request(&request, (char*)enc_domain, enc_size);
free(enc_domain);
if (wc_domain)
{
encrypt_domain_v1(params, wc_domain, &enc_domain, &enc_size);
append_to_request(&request, (char*)enc_domain, enc_size);
free(enc_domain);
}
}
if (params->verbose)
printf("Request: %s\n", request);
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, params->server);
curl_easy_setopt(curl, CURLOPT_PORT, params->server_port);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, params->verify_ssl_peer);
if (params->ca_path)
curl_easy_setopt(curl, CURLOPT_CAINFO, params->ca_path);
curl_easy_setopt(curl, CURLOPT_POST, 1);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request);
if (params->verbose)
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)response);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK)
{
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
goto end;
}
token = strtok_r(response, "\n", &saveptr);
while (token)
{
if (params->verbose)
printf("Parse %s\n", token);
cur_ptr = token;
if (!strcmp(token, "<end>"))
break;
else if (!STRNCMP(token, "protocol"))
{
cur_ptr += sizeof("protocol"); // includes "="
if (STRNCMP(cur_ptr, "gpass-"))
{
fprintf(stderr, "Error: Unknown server protocol %s\n", token);
break;
}
else
{
cur_ptr += sizeof("gpass-")-1;
if (atoi(cur_ptr) > SERVER_PROTOCOL)
{
fprintf(stderr, "Error: Cannot handle server protocol %s\n", token);
break;
}
}
}
else if (!STRNCMP(token, "pass"))
{
cur_ptr += sizeof("pass"); // includes "="
if ((strlen(cur_ptr)/2) > sizeof(password))
{
fprintf(stderr, "Error: retrieved password is too big !\n");
goto end;
}
hex_to_bin(password, (unsigned char*)cur_ptr, strlen(cur_ptr));
if (matched_key >= crypto_v1_index)
{
// Crypto v1
EVP_DecryptInit(s_cipher_ctx, EVP_aes_256_ecb(), params->derived_master_key, NULL);
EVP_CipherUpdate(s_cipher_ctx, password, &res, password, strlen(cur_ptr)/2);
// Remove salt
password[strlen((char*)password)-3] = 0;
}
else
{
EVP_DecryptInit(s_cipher_ctx, EVP_aes_256_cbc(), params->derived_master_key, params->global_iv);
EVP_CipherUpdate(s_cipher_ctx, password, &res, password, strlen(cur_ptr)/2);
// Remove salt
len = strlen((char*)password);
memmove(password, &password[3], len-3);
password[len-3] = 0;
}
display_password((char*)password, DISPLAY_TIME);
ret = 0;
goto end;
}
else if (!STRNCMP(token, "pbkdf2_level"))
{
cur_ptr += sizeof("pbkdf2_level"); // includes "="
if (atoi(cur_ptr) != params->pbkdf2_level)
{
params->pbkdf2_level = atoi(cur_ptr);
ret = 1;
break;
}
}
else if (!STRNCMP(token, "matched_key"))
{
cur_ptr += sizeof("matched_key"); // includes "="
matched_key = atoi(cur_ptr);
}
else
{
fprintf(stderr, "Error: Unknown server response %s\n", token);
break;
}
token = strtok_r(NULL, "\n", &saveptr);
}
if (ret)
printf("Password not found\n");
end:
free(request);
return ret;
}
static void init_parameters(struct gpass_parameters* params)
{
memset (params, 0, sizeof(*params));
params->pbkdf2_level = DEFAULT_PBKDF2_LEVEL;
params->server_port = DEFAULT_SERVER_PORT;
params->verify_ssl_peer = 1;
params->crypto_v1_compatible = 1; // For now, in the next version it must a command line parameter
}
static void release_parameters(struct gpass_parameters* params)
{
if (params->server) free(params->server);
if (params->salt) free(params->salt);
if (params->domain) free(params->domain);
if (params->username) free(params->username);
if (params->orig_master_key) free(params->orig_master_key);
if (params->derived_master_key) free(params->derived_master_key);
if( params->ca_path) free(params->ca_path);
if (params->global_iv) free(params->global_iv);
}
static int check_parameters(struct gpass_parameters* params)
{
if (!params->server)
{
fprintf(stderr, "Error: server not set\n");
return 1;
}
if (!params->domain)
{
fprintf(stderr, "Error: gpass domain not set\n");
return 1;
}
if (!params->username)
{
fprintf(stderr, "Error: username not set\n");
return 1;
}
return 0;
}
static int gpass_ini_handler(void* user, const char* section,
const char* name, const char* value)
{
struct gpass_parameters* params = (struct gpass_parameters*) user;
if (!STRNCMP(name, "ca_path"))
{
if (params->ca_path) free(params->ca_path);
params->ca_path = strdup(value);
}
else if (!STRNCMP(name, "pbkdf2_level"))
params->pbkdf2_level = atoi(value);
else if (!STRNCMP(name, "verify_ssl_peer"))
params->verify_ssl_peer = atoi(value);
else if (!STRNCMP(name, "server_port"))
{
params->server_port = atoi(value);
params->port_set = 1;
}
else if (!STRNCMP(name, "server"))
{
if (params->server) free(params->server);
params->server = strdup(value);
}
else
fprintf(stderr, "Error: Unknown key '%s' in config file\n", name);
return 1;
}
static void usage(char* program_name)
{
fprintf(stderr, "Usage: %s [-f config_file] [-p server_port] [-c CA_certificate_path] [-l PBKDF2_level] [-s gpass_server] [-v] -d domain -u username\n",
program_name);
exit(EXIT_FAILURE);
}
int main(int argc, char** argv)
{
struct gpass_parameters params;
int opt, ret = 0;
char* tmp;
char* config_file, *home;
if (argc == 1)
usage(argv[0]);
init_parameters(&params);
home = getenv("HOME");
if (home)
{
config_file = malloc(strlen(home)+1+sizeof(DEFAULT_CONFIG_FILE));
sprintf(config_file, "%s/" DEFAULT_CONFIG_FILE, home);
ini_parse(config_file, gpass_ini_handler, &params);
free(config_file);
}
while ((opt = getopt(argc, argv, "c:d:f:l:np:s:u:vh")) != -1) {
switch (opt) {
case 'c':
if (params.ca_path) free(params.ca_path);
params.ca_path = strdup(optarg);
break;
case 'd':
if (params.domain) free(params.domain);
params.domain = strdup(optarg);
break;
case 'f':
ini_parse(optarg, gpass_ini_handler, &params);
break;
case 'l':
params.pbkdf2_level = atoi(optarg);
break;
case 'n':
params.verify_ssl_peer = 0;
break;
case 'p':
params.server_port = atoi(optarg);
params.port_set = 1;
break;
case 's':
if (params.server) free(params.server);
params.server = strdup(optarg);
break;
case 'u':
if (params.username) free(params.username);
params.username = strdup(optarg);
break;
case 'v':
params.verbose++;
break;
case 'h':
case '?':
default: /* '?' */
usage(argv[0]);
}
}
ret = check_parameters(&params);
if (ret)
goto end;
// Manage server, server_port and salt
if (!STRNCMP(params.server, "http://"))
{
if (!params.port_set)
params.server_port = 80;
params.salt = strdup(&params.server[7]);
}
else if (!STRNCMP(params.server, "https://"))
{
if (!params.port_set)
params.server_port = 443;
params.salt = strdup(&params.server[8]);
}
// Manage domain
if (!STRNCMP(params.domain, "http://"))
{
tmp = strdup(&params.domain[7]);
free(params.domain);
params.domain = tmp;
}
else if (!STRNCMP(params.domain, "https://"))
{
tmp = strdup(&params.domain[8]);
free(params.domain);
params.domain = tmp;
}
// Remove query part of domain (a.com[/XXXX])
for (tmp=params.domain; *tmp; tmp++)
{
if (*tmp == '/')
{
*tmp = 0;
break;
}
}
s_md_ctx = EVP_MD_CTX_new();
s_md_256 = EVP_sha256();
EVP_DigestInit(s_md_ctx, s_md_256);
s_cipher_ctx = EVP_CIPHER_CTX_new();
// Let's go
tmp = getpass("Enter master key: ");
if (!tmp)
goto end;
params.orig_master_key = strdup(tmp);
derive_master_key(&params);
// Ctrl+C
signal(SIGINT, signal_handler);
// Ctrl+Z
signal(SIGTSTP, signal_handler);
ret = ask_server(&params);
// try again with new parameters
if (ret > 0)
{
derive_master_key(&params);
ask_server(&params);
}
end:
release_parameters(&params);
if (s_md_ctx) EVP_MD_CTX_free(s_md_ctx);
if (s_cipher_ctx) EVP_CIPHER_CTX_free(s_cipher_ctx);
return ret;
}

View File

@@ -1,59 +0,0 @@
gPass : global Password
=======================
Introduction
------------
Everyday we have a lot of passwords to manage corresponding to a lot of accounts we use. It's hard to remain all of these, moreover if we don't use it often. So, what most people do is to generate only a subset of passwords easy to remain. This implies two common errors :
* 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/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 bruteforce 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 home hostable (be careful on server down !). Moreover, with gPass, you can have multiple master key !
Usage
-----
The first thing to do is to populate your database (from your/a password server) with login/password/master key values. If you want to make strong password, there is a password generator. After that, configure your addon in "tools -> addons -> gPass -> preferences" 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 and fill "@@masterkey" in password field. Then submit and password will automatically be replaced by the one in the database (after addon decrypt it).
Technical details
-----------------
The two columns in database are "login" and "password".
login is compounded by "@@domain;login" encrypted with AES 256
password is salted and encrypted with AES 256
The key that encrypt these fields is PBKDF2(hmac-sha256, masterkey, password_server_url, 1000, 256)
For now the only addons made is for firefox. 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 ressources. For enhanced security, it's better to put the password server under https.
You can activate/deactivate user creation by setting $ADMIN_MODE in index.php.
A demonstration server is available [here](http://gpass-demo.soutade.fr). It's the default server of XPI package (user demo).
Client
------
Just install xpi package. You can have debug information by setting DEBUG in main.js (use it with firefox addon sdk).
Licence
-------
All the code is licenced under GPL v3. Source code is available [here](http://indefero.soutade.fr/p/gpass).

1
firefox_addon/README.md Symbolic link
View File

@@ -0,0 +1 @@
../README.md

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

42
firefox_addon/lib/hmac.js Normal file
View File

@@ -0,0 +1,42 @@
/*
Copyright (C) 2013 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 {Cc, Ci} = require("chrome");
var hmac = Cc["@mozilla.org/security/hmac;1"]
.createInstance(Ci.nsICryptoHMAC);
exports.hmac = {
hmac_init : function (key) {
var keyObject = Cc["@mozilla.org/security/keyobjectfactory;1"]
.getService(Ci.nsIKeyObjectFactory)
.keyFromString(Ci.nsIKeyObject.HMAC, key);
hmac.init(hmac.SHA256, keyObject);
},
hmac_digest : function (message) {
var data = new Uint8Array(message.length);
for(i=0; i<message.length; i++)
data[i] = message.charCodeAt(i);
hmac.update(data, data.length);
res = hmac.finish(false);
hmac.reset();
return res;
}
};

View File

@@ -1,404 +0,0 @@
/**
* @license jahashtable, a JavaScript implementation of a hash table. It creates a single constructor function called
* Hashtable in the global scope.
*
* http://www.timdown.co.uk/jshashtable/
* Copyright 2013 Tim Down.
* Version: 3.0
* Build date: 17 July 2013
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Hashtable = (function(UNDEFINED) {
var FUNCTION = "function", STRING = "string", UNDEF = "undefined";
// Require Array.prototype.splice, Object.prototype.hasOwnProperty and encodeURIComponent. In environments not
// having these (e.g. IE <= 5), we bail out now and leave Hashtable null.
if (typeof encodeURIComponent == UNDEF ||
Array.prototype.splice === UNDEFINED ||
Object.prototype.hasOwnProperty === UNDEFINED) {
return null;
}
function toStr(obj) {
return (typeof obj == STRING) ? obj : "" + obj;
}
function hashObject(obj) {
var hashCode;
if (typeof obj == STRING) {
return obj;
} else if (typeof obj.hashCode == FUNCTION) {
// Check the hashCode method really has returned a string
hashCode = obj.hashCode();
return (typeof hashCode == STRING) ? hashCode : hashObject(hashCode);
} else {
return toStr(obj);
}
}
function merge(o1, o2) {
for (var i in o2) {
if (o2.hasOwnProperty(i)) {
o1[i] = o2[i];
}
}
}
function equals_fixedValueHasEquals(fixedValue, variableValue) {
return fixedValue.equals(variableValue);
}
function equals_fixedValueNoEquals(fixedValue, variableValue) {
return (typeof variableValue.equals == FUNCTION) ?
variableValue.equals(fixedValue) : (fixedValue === variableValue);
}
function createKeyValCheck(kvStr) {
return function(kv) {
if (kv === null) {
throw new Error("null is not a valid " + kvStr);
} else if (kv === UNDEFINED) {
throw new Error(kvStr + " must not be undefined");
}
};
}
var checkKey = createKeyValCheck("key"), checkValue = createKeyValCheck("value");
/*----------------------------------------------------------------------------------------------------------------*/
function Bucket(hash, firstKey, firstValue, equalityFunction) {
this[0] = hash;
this.entries = [];
this.addEntry(firstKey, firstValue);
if (equalityFunction !== null) {
this.getEqualityFunction = function() {
return equalityFunction;
};
}
}
var EXISTENCE = 0, ENTRY = 1, ENTRY_INDEX_AND_VALUE = 2;
function createBucketSearcher(mode) {
return function(key) {
var i = this.entries.length, entry, equals = this.getEqualityFunction(key);
while (i--) {
entry = this.entries[i];
if ( equals(key, entry[0]) ) {
switch (mode) {
case EXISTENCE:
return true;
case ENTRY:
return entry;
case ENTRY_INDEX_AND_VALUE:
return [ i, entry[1] ];
}
}
}
return false;
};
}
function createBucketLister(entryProperty) {
return function(aggregatedArr) {
var startIndex = aggregatedArr.length;
for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) {
aggregatedArr[startIndex + i] = entries[i][entryProperty];
}
};
}
Bucket.prototype = {
getEqualityFunction: function(searchValue) {
return (typeof searchValue.equals == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals;
},
getEntryForKey: createBucketSearcher(ENTRY),
getEntryAndIndexForKey: createBucketSearcher(ENTRY_INDEX_AND_VALUE),
removeEntryForKey: function(key) {
var result = this.getEntryAndIndexForKey(key);
if (result) {
this.entries.splice(result[0], 1);
return result[1];
}
return null;
},
addEntry: function(key, value) {
this.entries.push( [key, value] );
},
keys: createBucketLister(0),
values: createBucketLister(1),
getEntries: function(destEntries) {
var startIndex = destEntries.length;
for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) {
// Clone the entry stored in the bucket before adding to array
destEntries[startIndex + i] = entries[i].slice(0);
}
},
containsKey: createBucketSearcher(EXISTENCE),
containsValue: function(value) {
var entries = this.entries, i = entries.length;
while (i--) {
if ( value === entries[i][1] ) {
return true;
}
}
return false;
}
};
/*----------------------------------------------------------------------------------------------------------------*/
// Supporting functions for searching hashtable buckets
function searchBuckets(buckets, hash) {
var i = buckets.length, bucket;
while (i--) {
bucket = buckets[i];
if (hash === bucket[0]) {
return i;
}
}
return null;
}
function getBucketForHash(bucketsByHash, hash) {
var bucket = bucketsByHash[hash];
// Check that this is a genuine bucket and not something inherited from the bucketsByHash's prototype
return ( bucket && (bucket instanceof Bucket) ) ? bucket : null;
}
/*----------------------------------------------------------------------------------------------------------------*/
function Hashtable() {
var buckets = [];
var bucketsByHash = {};
var properties = {
replaceDuplicateKey: true,
hashCode: hashObject,
equals: null
};
var arg0 = arguments[0], arg1 = arguments[1];
if (arg1 !== UNDEFINED) {
properties.hashCode = arg0;
properties.equals = arg1;
} else if (arg0 !== UNDEFINED) {
merge(properties, arg0);
}
var hashCode = properties.hashCode, equals = properties.equals;
this.properties = properties;
this.put = function(key, value) {
checkKey(key);
checkValue(value);
var hash = hashCode(key), bucket, bucketEntry, oldValue = null;
// Check if a bucket exists for the bucket key
bucket = getBucketForHash(bucketsByHash, hash);
if (bucket) {
// Check this bucket to see if it already contains this key
bucketEntry = bucket.getEntryForKey(key);
if (bucketEntry) {
// This bucket entry is the current mapping of key to value, so replace the old value.
// Also, we optionally replace the key so that the latest key is stored.
if (properties.replaceDuplicateKey) {
bucketEntry[0] = key;
}
oldValue = bucketEntry[1];
bucketEntry[1] = value;
} else {
// The bucket does not contain an entry for this key, so add one
bucket.addEntry(key, value);
}
} else {
// No bucket exists for the key, so create one and put our key/value mapping in
bucket = new Bucket(hash, key, value, equals);
buckets.push(bucket);
bucketsByHash[hash] = bucket;
}
return oldValue;
};
this.get = function(key) {
checkKey(key);
var hash = hashCode(key);
// Check if a bucket exists for the bucket key
var bucket = getBucketForHash(bucketsByHash, hash);
if (bucket) {
// Check this bucket to see if it contains this key
var bucketEntry = bucket.getEntryForKey(key);
if (bucketEntry) {
// This bucket entry is the current mapping of key to value, so return the value.
return bucketEntry[1];
}
}
return null;
};
this.containsKey = function(key) {
checkKey(key);
var bucketKey = hashCode(key);
// Check if a bucket exists for the bucket key
var bucket = getBucketForHash(bucketsByHash, bucketKey);
return bucket ? bucket.containsKey(key) : false;
};
this.containsValue = function(value) {
checkValue(value);
var i = buckets.length;
while (i--) {
if (buckets[i].containsValue(value)) {
return true;
}
}
return false;
};
this.clear = function() {
buckets.length = 0;
bucketsByHash = {};
};
this.isEmpty = function() {
return !buckets.length;
};
var createBucketAggregator = function(bucketFuncName) {
return function() {
var aggregated = [], i = buckets.length;
while (i--) {
buckets[i][bucketFuncName](aggregated);
}
return aggregated;
};
};
this.keys = createBucketAggregator("keys");
this.values = createBucketAggregator("values");
this.entries = createBucketAggregator("getEntries");
this.remove = function(key) {
checkKey(key);
var hash = hashCode(key), bucketIndex, oldValue = null;
// Check if a bucket exists for the bucket key
var bucket = getBucketForHash(bucketsByHash, hash);
if (bucket) {
// Remove entry from this bucket for this key
oldValue = bucket.removeEntryForKey(key);
if (oldValue !== null) {
// Entry was removed, so check if bucket is empty
if (bucket.entries.length == 0) {
// Bucket is empty, so remove it from the bucket collections
bucketIndex = searchBuckets(buckets, hash);
buckets.splice(bucketIndex, 1);
delete bucketsByHash[hash];
}
}
}
return oldValue;
};
this.size = function() {
var total = 0, i = buckets.length;
while (i--) {
total += buckets[i].entries.length;
}
return total;
};
}
Hashtable.prototype = {
each: function(callback) {
var entries = this.entries(), i = entries.length, entry;
while (i--) {
entry = entries[i];
callback(entry[0], entry[1]);
}
},
equals: function(hashtable) {
var keys, key, val, count = this.size();
if (count == hashtable.size()) {
keys = this.keys();
while (count--) {
key = keys[count];
val = hashtable.get(key);
if (val === null || val !== this.get(key)) {
return false;
}
}
return true;
}
return false;
},
putAll: function(hashtable, conflictCallback) {
var entries = hashtable.entries();
var entry, key, value, thisValue, i = entries.length;
var hasConflictCallback = (typeof conflictCallback == FUNCTION);
while (i--) {
entry = entries[i];
key = entry[0];
value = entry[1];
// Check for a conflict. The default behaviour is to overwrite the value for an existing key
if ( hasConflictCallback && (thisValue = this.get(key)) ) {
value = conflictCallback(key, thisValue, value);
}
this.put(key, value);
}
},
clone: function() {
var clone = new Hashtable(this.properties);
clone.putAll(this);
return clone;
}
};
Hashtable.prototype.toQueryString = function() {
var entries = this.entries(), i = entries.length, entry;
var parts = [];
while (i--) {
entry = entries[i];
parts[i] = encodeURIComponent( toStr(entry[0]) ) + "=" + encodeURIComponent( toStr(entry[1]) );
}
return parts.join("&");
};
return Hashtable;
})();
exports.Hashtable = Hashtable;

View File

@@ -1,5 +1,5 @@
/*
Copyright (C) 2013 Grégory Soutadé
Copyright (C) 2013-2014 Grégory Soutadé
This file is part of gPass.
@@ -19,15 +19,16 @@
var {Cc, Ci} = require("chrome");
var notifications = require("sdk/notifications");
var self = require("sdk/self");
var prefSet = require("sdk/simple-prefs");
// http://www.timdown.co.uk/jshashtable/
var Hashtable = require("jshashtable-3.0").Hashtable;
var pkdbf2 = require("pkdbf2").pkdbf2;
var aes = require("jsaes").aes;
var parseURI = require("parseuri").parseURI;
var prefSet = require("simple-prefs");
var pkdbf2 = require("lib/pkdbf2").pkdbf2;
var aes = require("lib/jsaes").aes;
var parseURI = require("lib/parseuri").parseURI;
var DEBUG = false;
var pkdbf2_level = getPref("pkdbf2_level");
var protocol_version = 3;
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) {
@@ -41,7 +42,7 @@ function a2hex(str) {
var hex = '';
for (var i = 0; i < str.length; i++)
{
c = str.charCodeAt(i).toString(16);
var c = str.charCodeAt(i).toString(16);
if (c.length == 1) c = "0" + c;
hex += c;
}
@@ -54,33 +55,294 @@ function debug(s)
console.log(s);
}
function notify(text, data)
{
var icon = self.data.url("gpass_icon_64.png");
notifications.notify({
title: "gPass",
text: text,
data: data,
iconUrl: icon,
});
}
function getPref(key)
{
return prefSet.prefs[key];
}
function setPref(key, value)
{
prefSet.prefs[key] = value;
}
function generate_request(domain, login, mkey)
{
var v = "@@" + domain + ";" + login;
debug("will encrypt " + v);
debug("with " + a2hex(mkey));
var enc = aes.encryptLongString(v, aes.init(mkey));
aes.finish();
debug("res " + a2hex(enc));
return enc;
}
function ask_server(form, field, logins, domain, wdomain, mkey, salt, submit)
{
var a, b;
mkey = pkdbf2.pkdbf2(mkey, salt, pkdbf2_level, 256/8);
keys = "";
for(a=0, b=logins.length; a<logins.length; a++)
{
enc = generate_request(domain, logins[a], mkey);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + a + "=" + a2hex(enc);
if (wdomain != "")
{
enc = generate_request(wdomain, logins[a], mkey);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + (b++) + "=" + a2hex(enc);
}
}
debug("Keys " + keys);
var gPassRequest = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
var ret = SERVER.OK;
// gPassRequest.addEventListener("progress", function(evt) { ; }, false);
gPassRequest.addEventListener("load", function(evt) {
var ciphered_password = "";
var server_pkdbf2_level = 0;
var server_version = 0;
var r = this.responseText.split("\n");
debug("resp " + r);
for(var a=0; a<r.length; a++)
{
debug("Analyse " + r[a]);
params = r[a].split("=");
if (params.length != 2 && params[0] != "<end>")
{
notify("Error : It seems that it's not a gPass server",
this.responseText);
ret = SERVER.FAILED;
break;
}
switch(params[0])
{
case "protocol":
debug("protocol : " + params[1]);
if (params[1].indexOf("gpass-") != 0)
{
notify("Error : It seems that it's not a gPass server",
this.responseText);
ret = SERVER.FAILED;
break;
}
server_protocol_version = params[1].match(/\d+/)[0];
if (server_protocol_version > protocol_version)
{
notify("Protocol version not supported, please upgrade your addon",
"Protocol version not supported, please upgrade your addon");
ret = SERVER.FAILED;
}
else
{
switch (server_protocol_version)
{
case 2:
server_pkdbf2_level = 1000;
break;
case 3:
// Version 3 : nothing special to do
break;
}
}
break;
case "pass":
ciphered_password = params[1];
break;
case "pkdbf2_level":
server_pkdbf2_level = parseInt(params[1].match(/\d+/)[0], 10);
if (server_pkdbf2_level != NaN &&
server_pkdbf2_level != pkdbf2_level &&
server_pkdbf2_level >= 1000) // Minimum level for PKDBF2 !
{
debug("New pkdbf2 level " + server_pkdbf2_level);
pkdbf2_level = server_pkdbf2_level;
setPref("pkdbf2_level", pkdbf2_level);
ret = SERVER.RESTART_REQUEST;
}
break;
case "<end>":
break;
default:
debug("Unknown command " + params[0]);
notify("Error : It seems that it's not a gPass server",
this.responseText);
ret = SERVER.FAILED;
break;
}
}
if (ret != SERVER.OK)
return;
if (ciphered_password != "")
{
debug("Ciphered password : " + ciphered_password);
clear_password = aes.decryptLongString(hex2a(ciphered_password), aes.init(mkey));
aes.finish();
// Remove trailing \0 and salt
clear_password = clear_password.replace(/\0*$/, "");
clear_password = clear_password.substr(0, clear_password.length-3);
debug("Clear password " + clear_password);
field.value = clear_password;
// Remove gPass event listener and submit again with clear password
if (submit)
{
form.removeEventListener("submit", on_sumbit, true);
form.submit();
}
else
{
notify("Password successfully replaced",
"Password successfully replaced");
}
}
else
{
debug("No password found");
ret = SERVER.FAILED;
notify("No password found in database",
"No password found in database");
}
}, false);
gPassRequest.addEventListener("error", function(evt) {
debug("error");
ret = false;
notify("Error",
"Error");
}, false);
debug("connect to " + getPref("account_url"));
gPassRequest.open("POST", getPref("account_url"), true);
gPassRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
gPassRequest.send(keys);
return ret;
}
function wildcard_domain(domain)
{
var parts = domain.split(".");
if (parts.length >= 3)
{
// Seems to be a two level root domain (ie zzz.xxx.co.uk ...)
if (parts[parts.length-2].lenght <= 3)
{
if (parts.length > 3)
return "*" + "." + parts[parts.length-3] + "." + parts[parts.length-2] + "." + parts[parts.length-1];
}
// Standard root domain (zzz.xxx.com)
else
return "*" + "." + parts[parts.length-2] + "." + parts[parts.length-1];
}
// Simple xxx.com
else if (parts.length == 2)
return "*" + "." + parts[0] + "." + parts[1];
return "";
}
function _add_name(logins, name)
{
for(var i=0; i<logins.length; i++)
if (logins[i] == name) return ;
logins.push(name);
}
function try_get_name(fields, type_filters, match)
{
var user = null;
var all_logins = new Array();
for (var i=0; i<fields.length; i++)
{
var field = fields[i];
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;
_add_name(all_logins, field.value);
}
}
}
}
if (user != null)
return new Array(user);
else
return all_logins;
}
function on_sumbit(e)
{
var form = this;
var fields = form.getElementsByTagName("input");
var my_map = new Hashtable();
domain = parseURI.parseUri(form.ownerDocument.baseURI);
var domain = parseURI.parseUri(form.ownerDocument.baseURI);
domain = domain["host"];
var wdomain = wildcard_domain(domain);
salt = parseURI.parseUri(prefSet.prefs["account_url"]);
var salt = parseURI.parseUri(getPref("account_url"));
salt = salt["host"] + salt["path"];
debug("salt " + salt);
type_filters = new Array();
// Get all <input type="text"> && <input type="email">
for (i=0; i<fields.length; i++)
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
if (!logins.length)
{
var field = fields[i];
if (field.getAttribute("type") == "text" || field.getAttribute("type") == "email")
{
if (field.hasAttribute("name") && field.value != "")
my_map.put(field.getAttribute("name"), field.value);
}
type_filters.push("password");
logins = try_get_name(fields, type_filters, false);
}
// Look for <input type="password" value="@@...">
for (i=0; i<fields.length; i++)
for (var i=0; i<fields.length; i++)
{
var field = fields[i];
@@ -88,126 +350,42 @@ function on_sumbit(e)
{
debug(field.value);
password = field.value;
if (password.indexOf("@@") != 0)
if (password.indexOf("@@") != 0 && password.indexOf("@_") != 0)
continue;
mkey = password.substring(2);
mkey = pkdbf2.pkdbf2(mkey, salt, 1000, 256/8);
user = null;
// Subset of common user field
if (my_map.containsKey("user")) user = my_map.get("user");
else if (my_map.containsKey("usr")) user = my_map.get("usr");
else if (my_map.containsKey("username")) user = my_map.get("username");
else if (my_map.containsKey("login")) user = my_map.get("login");
e.preventDefault();
// If no one found, use all
logins = (user != null) ? new Array(user) : my_map.values();
var ret = ask_server(form, field, logins, domain, wdomain, mkey, salt, (password.indexOf("@@") == 0));
keys = "";
for(a=0; a<logins.length; a++)
switch(ret)
{
v = "@@" + domain + ";" + logins[a];
debug("will encrypt " + v);
debug("with " + a2hex(mkey));
enc = aes.encryptLongString(v, aes.init(mkey));
aes.finish();
debug("res " + enc);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + a + "=" + a2hex(enc);
}
debug(keys);
// Need to do a synchronous request
var gPassRequest = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
var ret = true;
// gPassRequest.addEventListener("progress", function(evt) { ; }, false);
gPassRequest.addEventListener("load", function(evt) {
r = this.responseText.split("\n");
debug("resp " + r);
protocol = r[0].split("=");
if ((protocol.length == 2 && protocol[1] != "gpass-2") || protocol.length != 2)
case SERVER.OK:
break;
case SERVER.FAILED:
if (logins !== all_logins)
{
ret = false;
if (protocol.length == 2 && protocol[1].startsWith("gpass"))
{
notifications.notify({
title: "gPasss",
text: "Protocol version not supported, please upgrade your addon",
data: "Protocol version not supported, please upgrade your addon",
});
}
else
{
notifications.notify({
title: "gPasss",
text: "Error : It seems that it's not a gPass server",
data: this.responseText,
});
}
ret = ask_server(form, field, all_logins, domain, wdomain, mkey, salt, (password.indexOf("@@") == 0));
if (ret == SERVER.OK)
break;
}
else
{
if (r[1] != "<end>" && r[1].startsWith("pass="))
{
ciphered_password = r[1].split("=");
ciphered_password = ciphered_password[1];
debug("Ciphered password : " + ciphered_password);
clear_password = aes.decryptLongString(hex2a(ciphered_password), aes.init(mkey));
aes.finish();
// Remove salt
clear_password = clear_password.replace(/\0*$/, "");
clear_password = clear_password.substr(0, clear_password.length-3);
debug("Clear password " + clear_password);
field.value = clear_password;
}
else
{
debug("No password found");
ret = false;
notifications.notify({
title: "gPasss",
text: "No password found in database",
data: "No password found in database",
});
}
}
}, false);
gPassRequest.addEventListener("error", function(evt) {
debug("error");
ret = false;
notifications.notify({
title: "gPasss",
text: "Error",
data: "Error",
});
}, false);
debug("connect to " + prefSet.prefs["account_url"]);
gPassRequest.open("POST", prefSet.prefs["account_url"], false);
gPassRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
gPassRequest.send(keys);
if (!ret)
{
e.preventDefault();
return ret;
break;
case SERVER.RESTART_REQUEST:
i = -1; // Restart loop
break;
}
}
}
return false;
}
function document_loaded(event)
{
doc = event.target;
var doc = event.target;
// If there is a password in the form, add a "submit" listener
for(i=0; i<doc.forms.length; i++)
for(var i=0; i<doc.forms.length; i++)
{
var form = doc.forms[i];
var fields = form.getElementsByTagName("input");
@@ -239,12 +417,11 @@ observerService.addObserver(httpRequestObserver, "content-document-global-create
function self_test()
{
if((res = a2hex(hmac256("Jefe", "what do ya want for nothing?"))) !=
"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843")
console.log("HMAC256 failed " + res);
if((res = a2hex(pkdbf2.pkdbf2("password", "salt", 4096, 256/8))) !=
"c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a")
console.log("PKDBF2 failed " + res);
else
console.log("All is OK ! " + mkey);
}
console.log("All is OK ! ");
}
// self_test();

View File

@@ -17,27 +17,7 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
var {Cc, Ci} = require("chrome");
var hmac = Cc["@mozilla.org/security/hmac;1"]
.createInstance(Ci.nsICryptoHMAC);
function hmac_init(key) {
var keyObject = Cc["@mozilla.org/security/keyobjectfactory;1"]
.getService(Ci.nsIKeyObjectFactory)
.keyFromString(Ci.nsIKeyObject.HMAC, key);
hmac.init(hmac.SHA256, keyObject);
}
function hmac_digest(message) {
var data = new Uint8Array(message.length);
for(i=0; i<message.length; i++)
data[i] = message.charCodeAt(i);
hmac.update(data, data.length);
res = hmac.finish(false);
hmac.reset();
return res;
}
var hmac = require("lib/hmac").hmac;
exports.pkdbf2 = {
@@ -48,10 +28,10 @@ exports.pkdbf2 = {
var temp_res = "";
var temp_res2 = "";
hmac_init(password);
hmac.hmac_init(password);
for (i=1; result.length < outlen; i++)
{
temp = hmac_digest(salt +
temp = hmac.hmac_digest(salt +
String.fromCharCode((i & 0xff000000) >> 24) +
String.fromCharCode((i & 0x00ff0000) >> 16) +
String.fromCharCode((i & 0x0000ff00) >> 8) +
@@ -61,7 +41,7 @@ exports.pkdbf2 = {
for(a=1; a<iterations; a++)
{
temp2 = hmac_digest(temp);
temp2 = hmac.hmac_digest(temp);
temp_res2 = "";
for(b = 0; b<temp_res.length; b++)
temp_res2 += String.fromCharCode(temp_res.charCodeAt(b) ^ temp2.charCodeAt(b));
@@ -74,4 +54,4 @@ exports.pkdbf2 = {
return result.substr(0, outlen);
}
};
};

View File

@@ -1,16 +1,29 @@
{
"name": "gpass",
"fullName": "gPass",
"id": "jid1-eNs887pPJU8aNg",
"id": "jid1-eNs887pPJU8aNg@jetpack",
"main": "lib/main.js",
"description": "gPass : global password manager",
"author": "Grégory Soutadé",
"license": "GNU GPL v3",
"version": "0.1",
"preferences": [{
"homepage" : "http://indefero.soutade.fr/p/gpass",
"icon" : "data/gpass_icon_64.png",
"version": "0.7.0",
"preferences": [
{
"name": "account_url",
"title": "Account URL",
"description": "URL of your gPass account",
"type": "string",
"value": "http://gpass-demo.soutade.fr/demo"
}]
},
{
"name": "pkdbf2_level",
"title": "PKDBF2 Level",
"description": "Number of iterations used to derivate master key",
"type": "integer",
"value": 1000,
"hidden" : true
}
]
}

View File

@@ -0,0 +1,77 @@
function url_block_callback(details)
{
//console.log(details);
if (details.requestBody)
{
if (details.requestBody.formData)
{
for (var key in details.requestBody.formData)
{
for(var idx in details.requestBody.formData[key])
{
value = details.requestBody.formData[key][idx];
if (value.startsWith("@@") ||
value.startsWith("@_"))
return {cancel: true};
}
}
}
/*
// Analyse POST parameters
if (details.method == "POST" && details.requestBody.raw)
{
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};
}
*/
}
return {cancel: false};
}
browser.runtime.onMessage.addListener(
function(request) {
if (request.type == "notification")
{
options = {
type: "basic",
title : "gPass",
message : request.options.message,
iconUrl:browser.extension.getURL("icons/gpass_icon_64.png")
};
browser.notifications.create("gPass", options);
window.setTimeout(function() {browser.notifications.clear("gPass")}, 2000);
}
else if (request.type == "block_url")
{
browser.tabs.getCurrent().then(
function onGot(tab) {
if (tab)
{
browser.webRequest.onBeforeRequest.addListener(
url_block_callback,
{"urls":[request.options.url],
"types":["main_frame"],
"tabId":tab.id,
"windowId":tab.windowId
},
["blocking", "requestBody"]);
}
else
{
browser.webRequest.onBeforeRequest.addListener(
url_block_callback,
{"urls":[request.options.url], "types":["main_frame"]},
["blocking", "requestBody"]);
}
});
}
});

View File

@@ -0,0 +1,39 @@
/*
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/>.
*/
function getPref(key)
{
return browser.storage.local.get(key)
.then(
function (pref) {
if (!pref.hasOwnProperty(key))
return default_preferences[key];
return pref[key];
}
,
function (err) {
console.log("Error getting preference " + err);
}
);
}
function setPref(key, value)
{
browser.storage.local.set({key:value});
}

1
firefox_webextension/icons Symbolic link
View File

@@ -0,0 +1 @@
../chrome_addon/icons

1
firefox_webextension/lib Symbolic link
View File

@@ -0,0 +1 @@
../chrome_addon/lib/

View File

@@ -0,0 +1,37 @@
{
"manifest_version": 2,
"name": "gPass",
"short_name": "gPass",
"version": "0.8.2",
"description": "gPass : global password manager",
"icons" : {"16":"icons/gpass_icon_16.png", "32":"icons/gpass_icon_32.png", "64":"icons/gpass_icon_64.png", "128":"icons/gpass_icon_128.png"},
"author" : "Grégory Soutadé",
"homepage_url" : "http://indefero.soutade.fr/p/gpass",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["lib/parseuri.js", "lib/misc.js", "compat.js", "lib/main.js"],
"run_at" : "document_idle",
"all_frames" : true
}
],
"background": {
"persistent": true,
"scripts": ["background.js"]
},
"options_ui": { "page":"options.html" },
"permissions": [
"<all_urls>",
"notifications",
"webRequest",
"webRequestBlocking",
"tabs",
"storage",
"activeTab"
]
}

View File

@@ -0,0 +1 @@
../chrome_addon/options.html

View File

@@ -0,0 +1,42 @@
var default_preferences = {"pbkdf2_level": 1000,
"account_url": "https://gpass-demo.soutade.fr/demo",
"crypto_v1_compatible": true};
function save() {
var account_url = document.getElementById('account_url').value;
var pbkdf2 = document.getElementById('pbkdf2').value;
var crypto_v1_compatible = document.getElementById('crypto_v1_compatible').checked;
browser.storage.local.set({
"account_url":account_url,
"pbkdf2_level":pbkdf2,
"crypto_v1_compatible": crypto_v1_compatible,
})
.then(function ok() { alert("Saved"); },
function err() { alert("Cannot save your preferences");}
);
}
function restoreOptions()
{
document.getElementById('account_url').value = default_preferences['account_url'];
document.getElementById('pbkdf2').value = default_preferences['pbkdf2_level'];
document.getElementById('crypto_v1_compatible').checked = default_preferences["crypto_v1_compatible"];
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"];
if (prefs.hasOwnProperty("crypto_v1_compatible"))
document.getElementById('crypto_v1_compatible').checked = prefs["crypto_v1_compatible"];
}
);
}
document.getElementById('save').addEventListener("click", save);
document.addEventListener("DOMContentLoaded", restoreOptions);

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

BIN
resources/gpass_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
resources/gpass_icon.xcf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1,6 +1,6 @@
<?php
/*
Copyright (C) 2013 Grégory Soutadé
Copyright (C) 2013-2015 Grégory Soutadé
This file is part of gPass.
@@ -18,20 +18,48 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
include("conf.php");
function load_database()
{
global $REQUESTS_MIN_DELAY;
try {
$db = new SQLite3("./gpass.bdd", SQLITE3_OPEN_READONLY);
$db = new SQLite3("./gpass.bdd", SQLITE3_OPEN_READWRITE);
}
catch(Exception $e)
{
die("<b>Unable to load database for user $user !</b><br/>");
return null;
}
list($usec, $sec) = explode(" ", microtime());
$usec = $usec + $sec*1000;
try {
$last_time = $db->querySingle("SELECT last_access_time FROM conf");
if ($last_time <= $usec &&
($usec - $last_time) < $REQUESTS_MIN_DELAY)
{
// Brute force ??
$db->close();
return null;
}
$db->query("UPDATE conf SET last_access_time=$usec");
$db->close();
$db = new SQLite3("./gpass.bdd", SQLITE3_OPEN_READONLY);
}
catch(Exception $e)
{
$db->close();
die("<b>Unable to load database for user $user !</b><br/>");
return null;
}
return $db;
}
$PROTOCOL_VERSION = 2;
$PROTOCOL_VERSION = 4;
$db = load_database();
@@ -40,15 +68,18 @@ $res = "";
$statement = $db->prepare("SELECT password FROM gpass WHERE login=:login");
echo "protocol=gpass-$PROTOCOL_VERSION\n";
if ($PBKDF2_LEVEL != 1000)
echo "pbkdf2_level=$PBKDF2_LEVEL\n";
for ($i=0; isset($_POST["k$i"]); $i++)
for ($i=0; $i<$MAX_PASSWORDS_PER_REQUEST && isset($_POST["k$i"]); $i++)
{
$statement->bindValue(":login", $_POST["k$i"]);
$statement->bindValue(":login", addslashes($_POST["k$i"]));
$result = $statement->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
$result->finalize();
if (isset($row["password"]))
{
echo "matched_key=" . $i . "\n";
echo "pass=" . $row["password"] . "\n";
break;
}

88
server/conf.php Normal file
View File

@@ -0,0 +1,88 @@
<?php
/*
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/>.
*/
/*
User interface display or not ciphered passwords. Set to false avoid database leakage by user interface (but not by raw HTTP request).
*/
$VIEW_CIPHERED_PASSWORDS=true;
/*
Allows user creation
*/
$ADMIN_MODE=true;
/*
Number of iterations for PBKDF2 algorithm.
Minimum recommended level is 1000, but you can increase
this value to have a better security (need more computation
power).
!! Warning !! This impact master keys. So if you change
this value with existings masterkeys, they will unusable !
*/
$PBKDF2_LEVEL=1000;
/*
This is a security feature : It protects from database dump
and database purge without authentication.
When get all entries, instead of returning logins/passwords,
it returns "shadow logins". These are random values.
Shadow logins must be encrypted using masterkey and salt
(to generate a unique PBKDF2 derivation) that result in an access tokens.
With this access token, user has the right to get
encrypted login/password values and remove them.
It's a kind of challenge but requires more cpu bandwidth
(one derivation + two decryption for each password !).
This option is backward compatible with old version < 0.6
*/
$USE_SHADOW_LOGINS=1;
/*
Protection against DDoS.
Each request can contains multiple password combinations
(to support wildcards for example) and multiple names.
Currently only two passwords are sent from addon :
www.example.com
*.example.com
But, on future we may also consider 'www.example.*', '*.example.*' and lower case username.
For maximum security, you can set it to 2 or 4 if you want to be backward compatible
with addons/extions <= 0.7.
*/
$MAX_PASSWORDS_PER_REQUEST=10;
/*
Protection against brute force.
Minimum delay (in milliseconds) between two requests.
*/
$REQUESTS_MIN_DELAY=1000;
/*
Clear master keys and reset passwords after 15 minutes of inactivity
*/
$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

@@ -1,6 +1,6 @@
<?php
/*
Copyright (C) 2013 Grégory Soutadé
Copyright (C) 2013-2017 Grégory Soutadé
This file is part of gPass.
@@ -20,80 +20,21 @@
/*
login is stored as :
@@url;login
url;login + 16 bytes padding * \0 + sha256(url;login + padding)[8:24]
Password is salted (3 random characters) and encrypted
All is encrypted with AES256 and key : PKDBF2(hmac_sha256, master key, url, 1000)
All is encrypted with AES256-CBC and key PBKDF2(hmac_sha256, master key, server url, 1000)
level is server configurable
iv is PBKDF2(hmac_sha256, server url, master key, 1000)[0:16]
*/
$MAX_ENTRY_LEN = 512;
$USERS_PATH = "./users/";
$TARGET_DB_VERSION = 2;
function open_crypto($mkey)
function sanitize($val)
{
if (!isset($_SESSION['td']))
{
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
if ($td == false)
die("Unable to open mcrypt");
$ret = mcrypt_generic_init($td, hex2bin($mkey), '0000000000000000');
if ($ret < 0)
{
echo "<div class=\"error\">Unable to set key $ret</div>";
return null;
}
$_SESSION['td'] = $td;
}
else
$td = $_SESSION['td'];
return $td;
}
function decrypt($mkey, $val, $salted)
{
$td = open_crypto($mkey);
if ($td == null) return;
$val = mdecrypt_generic($td, hex2bin($val));
// Remove 0 added by encrypt
$val = str_replace("\0", '', $val);
// Remove salt
if ($salted)
$val = substr($val, 0, strlen($val)-3);
return $val;
}
function encrypt($mkey, $val, $salted)
{
global $MAX_ENTRY_LEN;
$td = open_crypto($mkey);
if ($td == null) return;
if ($salted)
{
$val .= dechex(rand(256,4095)); //between 0x100 and 0xfff
}
$val = mcrypt_generic($td, $val);
if (strlen($val) > $MAX_ENTRY_LEN)
{
echo "<div class=\"error\">Value to encrypt is too long</div>";
return null;
}
return bin2hex($val);
return (isset($_POST[$val])) ? addslashes($_POST[$val]) : "";
}
// From http://php.net/manual/en/function.copy.php
@@ -147,6 +88,65 @@ function create_user($user)
return false;
}
function _migrate_0($user, $db)
{
try {
$db->query("ALTER TABLE gpass ADD access_token VARCHAR(32)");
$db->query("ALTER TABLE gpass ADD shadow_login VARCHAR(32)");
$db->query("ALTER TABLE gpass ADD salt VARCHAR(32)");
$db->query("CREATE TABLE db_version(version INTEGER)");
$db->query("INSERT INTO db_version (version) VALUES (1)");
}
catch(Exception $e)
{
$db->close();
echo "<div class=\"error\">Unable to load database for user $user ! : $e</div>";
return -1;
}
return 0;
}
function _migrate_1($user, $db)
{
try {
$db->query("CREATE TABLE conf(db_version INTEGER, last_access_time INTEGER)");
$db->query("INSERT INTO conf VALUES(2, 0)");
}
catch(Exception $e)
{
$db->close();
echo "<div class=\"error\">Unable to load database for user $user ! : $e</div>";
return -1;
}
return 0;
}
function migrate_database($user, $db)
{
global $TARGET_DB_VERSION;
$migration_functions = ['_migrate_0', '_migrate_1'];
$version = $db->querySingle("SELECT db_version FROM conf");
if ($version == NULL || $version == -1)
{
$version = $db->querySingle("SELECT version FROM db_version");
if ($version == NULL || $version == -1)
$version = 0;
}
for($i=$version; $i<$TARGET_DB_VERSION; $i++)
{
if ($migration_functions[$i]($user, $db))
return -1;
}
return 0;
}
function load_database($user)
{
global $USERS_PATH;
@@ -160,14 +160,20 @@ function load_database($user)
return null;
}
if (migrate_database($user, $db))
return null;
// New access need to reset crypto
unset($_SESSION['td']);
return $db;
}
function add_entry($user, $login, $password)
function add_entry($user, $login, $password,
$shadow_login, $salt, $access_token)
{
global $USE_SHADOW_LOGINS;
$db = load_database($user);
if ($db == null)
@@ -176,25 +182,45 @@ function add_entry($user, $login, $password)
return false;
}
if ($USE_SHADOW_LOGINS && (strlen($shadow_login) != 32 ||
strlen($salt) != 32 || strlen($access_token) != 32))
{
$db->close();
echo "Shadow login not configured";
return false;
}
$count = $db->querySingle("SELECT COUNT(*) FROM gpass WHERE login='" . $login . "'");
if ($count != 0)
if ($count != NULL && $count != 0)
{
echo "Entry already exists";
return false;
}
$result = $db->query("INSERT INTO gpass ('login', 'password') VALUES ('" . $login . "', '" . $password . "')");
$result = $db->exec("INSERT INTO gpass ('login', 'password', 'shadow_login', 'salt', 'access_token') VALUES
('" . $login . "', '" . $password . "', '" . $shadow_login . "', '" . $salt . "', '" . $access_token . "')");
/* error_log("INSERT INTO gpass ('login', 'password', 'shadow_login', 'salt', 'access_token') VALUES */
/* ('" . $login . "', '" . $password . "', '" . $shadow_login . "', '" . $salt . "', '" . $access_token . "')"); */
$db->close();
echo "OK";
return true;
if (!$result)
{
echo "Error " . $db->lastErrorMsg();
return false;
}
else
{
echo "OK";
return true;
}
}
function delete_entry($user, $login)
function delete_entry($user, $login, $access_token)
{
global $USE_SHADOW_LOGINS;
$db = load_database($user);
if ($db == null)
@@ -203,37 +229,104 @@ function delete_entry($user, $login)
return false;
}
$db->query("DELETE FROM gpass WHERE login='" . $login . "'");
if ($USE_SHADOW_LOGINS)
{
$db_ac = $db->querySingle("SELECT access_token FROM gpass WHERE login='" . $login . "'");
if ($db_ac != NULL && strcmp($db_ac, $access_token))
{
$db->close();
echo "Bad access token";
return false;
}
}
$result = $db->exec("DELETE FROM gpass WHERE login='" . $login . "'");
$db->close();
echo "OK";
return true;
if (!$result)
{
echo "Error " . $db->lastErrorMsg();
return false;
}
else
{
echo "OK";
return true;
}
}
function update_entry($user, $mkey, $old_login, $url, $login, $password)
function update_entry($user, $mkey, $old_login, $url, $login, $password, $shadow_login, $salt, $old_access_token, $new_access_token)
{
if (delete_entry($user, $old_login))
return add_entry($user, $mkey, $url, $login, $password);
if (delete_entry($user, $old_login, $old_access_token))
return add_entry($user, $mkey, $url, $login, $password, $shadow_login, $salt, $new_access_token);
return false;
}
function list_entries($user)
{
global $USE_SHADOW_LOGINS;
$db = load_database($user);
if ($db == null) return;
$result = $db->query("SELECT * FROM gpass");
echo "entries\n";
$first = false;
header('Content-Type: application/json');
echo "{ \"entries\" : [\n";
while (($row = $result->fetchArray()))
{
echo $row['login'] . ";" . $row['password'] . "\n";
if ($first) echo ",";
else $first = true;
if (!strlen($row['shadow_login']) || !$USE_SHADOW_LOGINS)
echo "{\"login\" : \"" . $row['login'] . "\", \"password\" : \"" . $row['password'] . "\" }\n";
else
echo "{\"shadow_login\" : \"" . $row['shadow_login'] . "\", \"salt\" : \"" . $row['salt'] . "\" }\n";
}
echo "]}";
$db->close();
}
function get_secure_entries($user, $access_tokens)
{
$db = load_database($user);
if ($db == null) return;
$query = "SELECT access_token, login, password FROM gpass WHERE access_token IN (";
$first = false;
foreach (preg_split("/,/", $access_tokens) as $ac)
{
/* error_log($ac); */
if ($first) $query .= ", ";
else $first = true;
$query .= "'$ac'";
}
$query .= ")";
//error_log($query);
$result = $db->query($query);
header('Content-Type: application/json');
$first = false;
echo "{ \"entries\" : [\n";
while (($row = $result->fetchArray()))
{
if ($first) echo ",";
else $first = true;
echo "{\"access_token\" : \"" . $row['access_token'] . "\", \"login\" : \"" . $row['login'] . "\", \"password\" : \"" . $row['password'] . "\" }\n";
}
echo "]}";
$db->close();
}
?>

View File

@@ -1,6 +1,6 @@
<?php
/*
Copyright (C) 2013 Grégory Soutadé
Copyright (C) 2013-2017 Grégory Soutadé
This file is part of gPass.
@@ -18,31 +18,54 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
include('conf.php');
include('functions.php');
session_start();
$VIEW_CIPHERED_PASSWORDS=true;
$ADMIN_MODE=true;
$user = "";
$user = '';
if ($ADMIN_MODE && isset($_POST['create_user']))
{
if (create_user($_POST['user']))
$user = addslashes($_POST['user']);
if (create_user($user))
$user = $_POST['user'];
else
$user = '';
}
else
{
$user = sanitize('user');
$login = sanitize('login');
$shadow_login = sanitize('shadow_login');
$password = sanitize('password');
$access_token = sanitize('access_token');
$access_tokens = sanitize('access_tokens');
$salt = sanitize('salt');
if (isset($_POST['get_secure_passwords']) && isset($_POST['user']) &&
isset($_POST['access_tokens']))
return get_secure_entries($user, $access_tokens);
if (isset($_POST['get_passwords']) && isset($_POST['user']))
return list_entries($_POST['user']);
return list_entries($user);
if (isset($_POST['add_entry']) && isset($_POST['user']) &&
isset($_POST['login']) && isset($_POST['password']))
return add_entry($_POST['user'], $_POST['login'], $_POST['password']);
isset($_POST['login']) && isset($_POST['password']) &&
isset($_POST['shadow_login']) && isset($_POST['salt']) &&
isset($_POST['access_token']) )
return add_entry($user,
$login,
$password,
$shadow_login,
$salt,
$access_token);
if (isset($_POST['delete_entry']) && isset($_POST['user']) &&
isset($_POST['login']))
return delete_entry($_POST['user'], $_POST['login']);
isset($_POST['login']) && isset($_POST['access_token']))
return delete_entry($user,
$login,
$access_token);
}
?>
@@ -50,17 +73,39 @@ else
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
<link rel="stylesheet" type="text/css" href="ressources/gpass.css" />
<script src="ressources/jsaes.js"></script>
<script src="ressources/jssha256.js"></script>
<script src="ressources/hmac.js"></script>
<script src="ressources/pkdbf2.js"></script>
<script src="ressources/gpass.js"></script>
<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) {
document.getElementById("buttonTop").className = (window.pageYOffset > 500) ? "cVisible" : "cInvisible";
};
});
function scrollToTop()
{
if (window.pageYOffset == 0)
return;
target = (window.innerHeight) ? window.innerHeight/5 : 200;
toScroll = (window.pageYOffset > target) ? target : window.pageYOffset;
window.scrollBy(0, -toScroll);
setTimeout(scrollToTop, 24);
}
</script>
<script src="resources/misc.js"></script>
<script src="resources/gpass.js"></script>
<script src="resources/pwdmeter.js"></script>
<title>gPass : global Password</title>
</head>
<body onload="start();">
<div><a id="buttonTop" class="cInvisible" onclick="scrollToTop();"></a></div>
<div id="logo">
<a href="http://indefero.soutade.fr/p/gpass"><img src="ressources/gpass.png" alt="logo"/></a>
<a href="http://indefero.soutade.fr/p/gpass"><img src="resources/gpass.png" alt="logo"/></a>
</div>
<div id="admin" <?php if (!$ADMIN_MODE) echo "style=\"display:none\"";?> >
@@ -83,7 +128,7 @@ if ($count == 0)
echo "<b>No user found</b><br/>\n";
else
{
echo "<b>User</b> <select id=\"selected_user\" name=\"user\" onchange=\"document.getElementById('master_key').value = ''\">" . "\n";
echo "<b>User</b> <select id=\"selected_user\" name=\"user\" onchange=\"document.getElementById('master_key').value = '';update_master_key(false);\">" . "\n";
foreach($users as $u)
{
if (is_dir("./users/" . $u) && $u[0] != '_' && $u[0] != '.')
@@ -96,8 +141,8 @@ else
}
}
echo "</select>\n";
echo ' <b>Master key </b> <input id="master_key" type="password" onkeypress="if (event.keyCode == 13) update_master_key();"/>';
echo "<input type=\"button\" value=\"See\" onclick=\"update_master_key();\" />" . "\n";
echo ' <b>Master key </b> <input id="master_key" type="password" onkeypress="if (event.keyCode == 13) update_master_key(true);"/>';
echo "<input type=\"button\" value=\"See\" onclick=\"update_master_key(true);\" />" . "\n";
if (!isset($_SERVER['HTTPS']))
echo "<div id=\"addon_address\">Current addon address is : http://" . $_SERVER['SERVER_NAME'] . "/" . $user . "</div>\n";
@@ -105,8 +150,6 @@ else
echo "<div id=\"addon_address\">Current addon address is : https://" . $_SERVER['SERVER_NAME'] . "/" . $user . "</div>\n";
}
?>
<div id="passwords">
</div>
<div id="add_new_password">
<?php
global $user;
@@ -118,9 +161,41 @@ if ($user != "")
echo 'URL <input type="text" name="url"/>';
echo 'login <input type="text" name="login" />';
echo 'password <input id="new_password" type="text" name="password"/>';
echo 'master key <input type="password" name="mkey" onkeypress="if (event.keyCode == 13) add_password();"/>';
echo 'master key <input type="text" name="mkey" onkeypress="if (event.keyCode == 13) 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();\"/>";
echo "<br />";
echo '<div><a href="http://en.wikipedia.org/wiki/Password_strength">Master key strength</a><div id="scorebarBorder"><div id="score">0%</div><div id="scorebar">&nbsp;</div></div></div>';
}
?>
</div>
<div id="passwords">
</div>
<div id="update_masterkey">
<?php
global $user;
if ($user != "")
{
echo "<b>Update Masterkey</b><br/>\n";
echo 'Old master key <input type="text" id="oldmkey"/>';
echo 'New master key <input type="text" id="newmkey" onkeyup="chkPass(this.value);"/>';
echo '<input type="button" value="Update masterkey" onClick="update_masterkey();"/>';
}
?>
</div>
<div id="export_database">
<?php
global $user;
if ($user != "")
{
echo "<b>Export</b><br/>\n";
echo '<input type="button" value="Export" onclick="export_database();"/>';
echo '<a id="export_link">Download</a>';
}
?>
</div>

View File

@@ -1 +1,3 @@
CREATE TABLE gpass(login VARCHAR(512) PRIMARY KEY, password VARCHAR(512));
CREATE TABLE gpass(login VARCHAR(512) PRIMARY KEY, password VARCHAR(512), shadow_login VARCHAR(32), salt VARCHAR(32), access_token VARCHAR(32));
CREATE TABLE conf(db_version INTEGER, last_access_time INTEGER);
INSERT INTO conf VALUES (2, 0);

BIN
server/ref/gpass.bdd Executable file → Normal file

Binary file not shown.

View File

@@ -18,44 +18,6 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
function load_database()
{
try {
$db = new SQLite3("./gpass.bdd", SQLITE3_OPEN_READONLY);
}
catch(Exception $e)
{
die("<b>Unable to load database for user $user !</b><br/>");
return null;
}
return $db;
}
$PROTOCOL_VERSION = 2;
$db = load_database();
$res = "";
$statement = $db->prepare("SELECT password FROM gpass WHERE login=:login");
echo "protocol=gpass-$PROTOCOL_VERSION\n";
for ($i=0; isset($_POST["k$i"]); $i++)
{
$statement->bindValue(":login", $_POST["k$i"]);
$result = $statement->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
$result->finalize();
if (isset($row["password"]))
{
echo "pass=" . $row["password"] . "\n";
break;
}
}
$statement->close();
echo "<end>";
include "../../_user";
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

BIN
server/resources/favicon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

162
server/resources/gpass.css Executable file
View File

@@ -0,0 +1,162 @@
body {
background-image:linear-gradient(#0096ff 30%, white);
height:100%; width:100%;
}
#logo {
display:block;
margin-left:auto;
margin-right:auto;
margin-top:30px;
margin-bottom:40px;
text-align:center;
}
#admin {
border-style:solid;
border-width:5px;
border-color:red;
padding : 15px;
margin : 15px;
}
#admin form {
text-align : center;
}
#user {
border-style:solid;
border-width:5px;
border-color:green;
padding : 15px;
margin : 15px;
text-align:center;
}
#user input {
margin-left : 15px;
margin-right : 30px;
margin-top : 10px;
margin-bottom : 10px;
}
#select_user {
text-align : center;
}
#passwords {
border-style:solid;
border-width:5px;
border-color:grey;
padding : 15px;
margin : 15px;
}
#addon_address {
padding:10px;
text-align:center;
font-size:xx-large;
}
.hash {
width : 700px;
}
#add_new_password {
border-style:solid;
border-width:5px;
border-color:blue;
padding : 15px;
margin : 15px;
}
#update_masterkey {
border-style:solid;
border-width:5px;
border-color:yellow;
padding : 15px;
margin : 15px;
}
#export_database {
border-style:solid;
border-width:5px;
border-color:pink;
padding : 15px;
margin : 15px;
}
#export_link {
display:none;
visibility:hidden;
}
.error {
text-align:center;
color:red;
font-weight:bold;
font-size:xx-large;
}
#scorebarBorder {
background: #333;
border: 1px #000 solid;
height: 16px;
margin-bottom: 2px;
margin-left:auto;
margin-right:auto;
width: 100px;
}
#score {
color: rgb(0, 0, 0);
font-size: 85%;
position: absolute;
text-align: center;
width: 100px;
z-index: 10;
font-weight:bold;
}
#scorebar {
background-image: url(/resources/bg_strength_gradient.jpg);
background-repeat: no-repeat;
background-position: 0 0;
position:absolute;
width: 100px;
z-index: 0;
}
/* From http://www.trucsweb.com/tutoriels/javascript/retour-haut/ */
a#buttonTop{
border-radius:3px;
padding:0 10px 10px 10px;
font-size:3em;
text-align:center;
color:#fff;
background:rgba(0, 0, 0, 0.25);
position:fixed;
right:3%;
opacity:1;
z-index:99999;
transition:all ease-in 0.2s;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
text-decoration: none;
}
a#buttonTop:before{ content: "\25b2"; }
a#buttonTop:hover{
background:rgba(0, 0, 0, 1);
transition:all ease-in 0.2s;
}
a#buttonTop.cInvisible{
bottom:-35px;
opacity:0;
transition:all ease-in 0.5s;
}
a#buttonTop.cVisible{
bottom:20px;
opacity:1;
}

1000
server/resources/gpass.js Executable file

File diff suppressed because it is too large Load Diff

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

220
server/resources/misc.js Normal file
View File

@@ -0,0 +1,220 @@
/*
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"};
var browser = browser;
if (typeof chrome !== 'undefined')
browser = chrome;
var crypto = crypto || window.crypto;
// https://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
function str2ab2(str) {
var chars = []
for (var i=0, strLen=str.length; i < strLen; i++) {
chars.push(str.charCodeAt(i));
}
return new Uint8Array(chars);
}
function str2ab(str) {
var buf = new ArrayBuffer(str.length); // 2 bytes for each char
// var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
var bufView = new Uint8Array(buf);
for (var i=0, strLen=str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return bufView;
}
function crypto_pbkdf2(mkey, salt, level)
{
AESCBC = {
name: "AES-CBC",
length: 256,
}
var key = str2ab(mkey);
return crypto.subtle.importKey("raw", key, {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"])
.then(function(key){
//sha-256
return crypto.subtle.deriveKey({
name: "PBKDF2",
salt: str2ab(salt),
iterations: level,
hash: "SHA-256",
}, key, AESCBC, false, ["encrypt", "decrypt", "unwrapKey", "wrapKey"])
.then(function(key) {
return key;
})
.catch(function(err){
console.log("Error derive key " + err);
});
})
.catch(function(err) {
console.log("Error import key" + err);
});
}
function simple_pbkdf2(mkey, salt, level)
{
AESCBC = {
name: "AES-CBC",
length: 256,
}
var key = str2ab(mkey);
return crypto.subtle.importKey("raw", key, {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"])
.then(function(key){
//sha-256
return crypto.subtle.deriveKey({
name: "PBKDF2",
salt: str2ab(salt),
iterations: level,
hash: "SHA-256",
}, key, AESCBC, true, ["unwrapKey", "wrapKey"])
.then(function(key) {
return crypto.subtle.exportKey("raw", key)
.then(function (key) {
return ab2str(key);
});
})
.catch(function(err){
console.log("Error derive key " + err);
});
})
.catch(function(err) {
console.log("Error import key" + err);
});
}
function _encrypt(mkey, iv, data)
{
while ((data.length % 16))
data += "\0";
data = str2ab(data);
promise = mkey.then(function(mkey){
return crypto.subtle.encrypt({
name: "AES-CBC",
iv: iv
}, mkey, data)})
.then(function(encrypted) {
return ab2str(encrypted);
})
.catch(function(encryption) {
console.log("Encryption rejected " + encryption);
});
return promise;
}
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]);
pkcs7_padding = await _encrypt(mkey, nulliv, ab2str(pkcs7_padding));
data = str2ab(data + pkcs7_padding);
promise = mkey.then(function(mkey){
return crypto.subtle.decrypt({
name: "AES-CBC",
iv: iv
}, mkey, data)})
.then(function(decrypted) {
return ab2str(decrypted);
})
.catch(function(decryption) {
console.log("Decryption rejected " + decryption);
});
return promise;
}
async function encrypt_ecb(mkey, data)
{
var result = "";
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
while (data.length > 16)
{
res = await _encrypt(mkey, nulliv, data.slice(0, 16));
// Remove PKCS7 padding
result += res.slice(0, 16);
data = data.slice(16);
}
res = await _encrypt(mkey, nulliv, data);
result += res.slice(0, 16);
return result;
}
async function decrypt_ecb(mkey, data)
{
var result = "";
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
while (data.length > 16)
{
res = await _decrypt(mkey, nulliv, data.slice(0, 16));
// Remove PKCS7 padding
result += res.slice(0, 16);
data = data.slice(16);
}
res = await _decrypt(mkey, nulliv, data);
result += res.slice(0, 16);
return result;
}
async function encrypt_cbc(mkey, iv, data)
{
var result = await _encrypt(mkey, str2ab(iv), data);
// Remove PKCS7 padding
return result.slice(0, result.length-16);
}
async function decrypt_cbc(mkey, iv, data)
{
var result = await _decrypt(mkey, str2ab(iv), data);
// Remove PKCS7 padding
return result.slice(0, result.length-16);
}
async function digest(data)
{
return crypto.subtle.digest("SHA-256", str2ab(data)).then(function (hash) {
return ab2str(hash);
});
}

View File

@@ -17,7 +17,7 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
function pkdbf2 (password, salt, iterations, outlen) {
function pbkdf2 (password, salt, iterations, outlen) {
var result = "";
var temp = "";
var temp2 = "";

View File

@@ -0,0 +1,341 @@
/*
** Created by: Jeff Todnem (http://www.todnem.com/)
** Created on: 2007-08-14
** Last modified: 2010-05-03
**
** License Information:
** -------------------------------------------------------------------------
** Copyright (C) 2007 Jeff Todnem
**
** This program 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 2 of the License, or (at your
** option) any later version.
**
** This program 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 this program; if not, write to the Free Software Foundation, Inc.,
** 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
**
*/
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != "function") {
window.onload = func;
}
else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
};
}
}
function $() {
var arrElms = [];
for (var i=0; i < arguments.length; i++) {
var elm = arguments[i];
if (typeof(elm == "string")) { elm = document.getElementById(elm); }
if (arguments.length == 1) { return elm; }
arrElms.push(elm);
}
return arrElms;
}
String.prototype.strReverse = function() {
var newstring = "";
for (var s=0; s < this.length; s++) {
newstring = this.charAt(s) + newstring;
}
return newstring;
//strOrig = ' texttotrim ';
//strReversed = strOrig.revstring();
};
function chkPass(pwd) {
var oScorebar = $("scorebar");
var oScore = $("score");
var oComplexity = $("complexity");
// Simultaneous variable declaration and value assignment aren't supported in IE apparently
// so I'm forced to assign the same value individually per var to support a crappy browser *sigh*
var nScore=0, nLength=0, nAlphaUC=0, nAlphaLC=0, nNumber=0, nSymbol=0, nMidChar=0, nRequirements=0, nAlphasOnly=0, nNumbersOnly=0, nUnqChar=0, nRepChar=0, nRepInc=0, nConsecAlphaUC=0, nConsecAlphaLC=0, nConsecNumber=0, nConsecSymbol=0, nConsecCharType=0, nSeqAlpha=0, nSeqNumber=0, nSeqSymbol=0, nSeqChar=0, nReqChar=0, nMultConsecCharType=0;
var nMultRepChar=1, nMultConsecSymbol=1;
var nMultMidChar=2, nMultRequirements=2, nMultConsecAlphaUC=2, nMultConsecAlphaLC=2, nMultConsecNumber=2;
var nReqCharType=3, nMultAlphaUC=3, nMultAlphaLC=3, nMultSeqAlpha=3, nMultSeqNumber=3, nMultSeqSymbol=3;
var nMultLength=4, nMultNumber=4;
var nMultSymbol=6;
var nTmpAlphaUC="", nTmpAlphaLC="", nTmpNumber="", nTmpSymbol="";
var sAlphaUC="0", sAlphaLC="0", sNumber="0", sSymbol="0", sMidChar="0", sRequirements="0", sAlphasOnly="0", sNumbersOnly="0", sRepChar="0", sConsecAlphaUC="0", sConsecAlphaLC="0", sConsecNumber="0", sSeqAlpha="0", sSeqNumber="0", sSeqSymbol="0";
var sAlphas = "abcdefghijklmnopqrstuvwxyz";
var sNumerics = "01234567890";
var sSymbols = ")!@#$%^&*()";
var sComplexity = "Too Short";
var sStandards = "Below";
var nMinPwdLen = 8;
if (document.all) { var nd = 0; } else { var nd = 1; }
if (pwd) {
nScore = parseInt(pwd.length * nMultLength);
nLength = pwd.length;
var arrPwd = pwd.replace(/\s+/g,"").split(/\s*/);
var arrPwdLen = arrPwd.length;
/* Loop through password to check for Symbol, Numeric, Lowercase and Uppercase pattern matches */
for (var a=0; a < arrPwdLen; a++) {
if (arrPwd[a].match(/[A-Z]/g)) {
if (nTmpAlphaUC !== "") { if ((nTmpAlphaUC + 1) == a) { nConsecAlphaUC++; nConsecCharType++; } }
nTmpAlphaUC = a;
nAlphaUC++;
}
else if (arrPwd[a].match(/[a-z]/g)) {
if (nTmpAlphaLC !== "") { if ((nTmpAlphaLC + 1) == a) { nConsecAlphaLC++; nConsecCharType++; } }
nTmpAlphaLC = a;
nAlphaLC++;
}
else if (arrPwd[a].match(/[0-9]/g)) {
if (a > 0 && a < (arrPwdLen - 1)) { nMidChar++; }
if (nTmpNumber !== "") { if ((nTmpNumber + 1) == a) { nConsecNumber++; nConsecCharType++; } }
nTmpNumber = a;
nNumber++;
}
else if (arrPwd[a].match(/[^a-zA-Z0-9_]/g)) {
if (a > 0 && a < (arrPwdLen - 1)) { nMidChar++; }
if (nTmpSymbol !== "") { if ((nTmpSymbol + 1) == a) { nConsecSymbol++; nConsecCharType++; } }
nTmpSymbol = a;
nSymbol++;
}
/* Internal loop through password to check for repeat characters */
var bCharExists = false;
for (var b=0; b < arrPwdLen; b++) {
if (arrPwd[a] == arrPwd[b] && a != b) { /* repeat character exists */
bCharExists = true;
/*
Calculate icrement deduction based on proximity to identical characters
Deduction is incremented each time a new match is discovered
Deduction amount is based on total password length divided by the
difference of distance between currently selected match
*/
nRepInc += Math.abs(arrPwdLen/(b-a));
}
}
if (bCharExists) {
nRepChar++;
nUnqChar = arrPwdLen-nRepChar;
nRepInc = (nUnqChar) ? Math.ceil(nRepInc/nUnqChar) : Math.ceil(nRepInc);
}
}
/* Check for sequential alpha string patterns (forward and reverse) */
for (var s=0; s < 23; s++) {
var sFwd = sAlphas.substring(s,parseInt(s+3));
var sRev = sFwd.strReverse();
if (pwd.toLowerCase().indexOf(sFwd) != -1 || pwd.toLowerCase().indexOf(sRev) != -1) { nSeqAlpha++; nSeqChar++;}
}
/* Check for sequential numeric string patterns (forward and reverse) */
for (var s=0; s < 8; s++) {
var sFwd = sNumerics.substring(s,parseInt(s+3));
var sRev = sFwd.strReverse();
if (pwd.toLowerCase().indexOf(sFwd) != -1 || pwd.toLowerCase().indexOf(sRev) != -1) { nSeqNumber++; nSeqChar++;}
}
/* Check for sequential symbol string patterns (forward and reverse) */
for (var s=0; s < 8; s++) {
var sFwd = sSymbols.substring(s,parseInt(s+3));
var sRev = sFwd.strReverse();
if (pwd.toLowerCase().indexOf(sFwd) != -1 || pwd.toLowerCase().indexOf(sRev) != -1) { nSeqSymbol++; nSeqChar++;}
}
/* Modify overall score value based on usage vs requirements */
/* General point assignment */
// $("nLengthBonus").innerHTML = "+ " + nScore;
if (nAlphaUC > 0 && nAlphaUC < nLength) {
nScore = parseInt(nScore + ((nLength - nAlphaUC) * 2));
sAlphaUC = "+ " + parseInt((nLength - nAlphaUC) * 2);
}
if (nAlphaLC > 0 && nAlphaLC < nLength) {
nScore = parseInt(nScore + ((nLength - nAlphaLC) * 2));
sAlphaLC = "+ " + parseInt((nLength - nAlphaLC) * 2);
}
if (nNumber > 0 && nNumber < nLength) {
nScore = parseInt(nScore + (nNumber * nMultNumber));
sNumber = "+ " + parseInt(nNumber * nMultNumber);
}
if (nSymbol > 0) {
nScore = parseInt(nScore + (nSymbol * nMultSymbol));
sSymbol = "+ " + parseInt(nSymbol * nMultSymbol);
}
if (nMidChar > 0) {
nScore = parseInt(nScore + (nMidChar * nMultMidChar));
sMidChar = "+ " + parseInt(nMidChar * nMultMidChar);
}
// $("nAlphaUCBonus").innerHTML = sAlphaUC;
// $("nAlphaLCBonus").innerHTML = sAlphaLC;
// $("nNumberBonus").innerHTML = sNumber;
// $("nSymbolBonus").innerHTML = sSymbol;
// $("nMidCharBonus").innerHTML = sMidChar;
/* Point deductions for poor practices */
if ((nAlphaLC > 0 || nAlphaUC > 0) && nSymbol === 0 && nNumber === 0) { // Only Letters
nScore = parseInt(nScore - nLength);
nAlphasOnly = nLength;
sAlphasOnly = "- " + nLength;
}
if (nAlphaLC === 0 && nAlphaUC === 0 && nSymbol === 0 && nNumber > 0) { // Only Numbers
nScore = parseInt(nScore - nLength);
nNumbersOnly = nLength;
sNumbersOnly = "- " + nLength;
}
if (nRepChar > 0) { // Same character exists more than once
nScore = parseInt(nScore - nRepInc);
sRepChar = "- " + nRepInc;
}
if (nConsecAlphaUC > 0) { // Consecutive Uppercase Letters exist
nScore = parseInt(nScore - (nConsecAlphaUC * nMultConsecAlphaUC));
sConsecAlphaUC = "- " + parseInt(nConsecAlphaUC * nMultConsecAlphaUC);
}
if (nConsecAlphaLC > 0) { // Consecutive Lowercase Letters exist
nScore = parseInt(nScore - (nConsecAlphaLC * nMultConsecAlphaLC));
sConsecAlphaLC = "- " + parseInt(nConsecAlphaLC * nMultConsecAlphaLC);
}
if (nConsecNumber > 0) { // Consecutive Numbers exist
nScore = parseInt(nScore - (nConsecNumber * nMultConsecNumber));
sConsecNumber = "- " + parseInt(nConsecNumber * nMultConsecNumber);
}
if (nSeqAlpha > 0) { // Sequential alpha strings exist (3 characters or more)
nScore = parseInt(nScore - (nSeqAlpha * nMultSeqAlpha));
sSeqAlpha = "- " + parseInt(nSeqAlpha * nMultSeqAlpha);
}
if (nSeqNumber > 0) { // Sequential numeric strings exist (3 characters or more)
nScore = parseInt(nScore - (nSeqNumber * nMultSeqNumber));
sSeqNumber = "- " + parseInt(nSeqNumber * nMultSeqNumber);
}
if (nSeqSymbol > 0) { // Sequential symbol strings exist (3 characters or more)
nScore = parseInt(nScore - (nSeqSymbol * nMultSeqSymbol));
sSeqSymbol = "- " + parseInt(nSeqSymbol * nMultSeqSymbol);
}
// $("nAlphasOnlyBonus").innerHTML = sAlphasOnly;
// $("nNumbersOnlyBonus").innerHTML = sNumbersOnly;
// $("nRepCharBonus").innerHTML = sRepChar;
// $("nConsecAlphaUCBonus").innerHTML = sConsecAlphaUC;
// $("nConsecAlphaLCBonus").innerHTML = sConsecAlphaLC;
// $("nConsecNumberBonus").innerHTML = sConsecNumber;
// $("nSeqAlphaBonus").innerHTML = sSeqAlpha;
// $("nSeqNumberBonus").innerHTML = sSeqNumber;
// $("nSeqSymbolBonus").innerHTML = sSeqSymbol;
/* Determine if mandatory requirements have been met and set image indicators accordingly */
var arrChars = [nLength,nAlphaUC,nAlphaLC,nNumber,nSymbol];
var arrCharsIds = ["nLength","nAlphaUC","nAlphaLC","nNumber","nSymbol"];
var arrCharsLen = arrChars.length;
// for (var c=0; c < arrCharsLen; c++) {
// var oImg = $('div_' + arrCharsIds[c]);
// var oBonus = $(arrCharsIds[c] + 'Bonus');
// $(arrCharsIds[c]).innerHTML = arrChars[c];
// if (arrCharsIds[c] == "nLength") { var minVal = parseInt(nMinPwdLen - 1); } else { var minVal = 0; }
// if (arrChars[c] == parseInt(minVal + 1)) { nReqChar++; oImg.className = "pass"; oBonus.parentNode.className = "pass"; }
// else if (arrChars[c] > parseInt(minVal + 1)) { nReqChar++; oImg.className = "exceed"; oBonus.parentNode.className = "exceed"; }
// else { oImg.className = "fail"; oBonus.parentNode.className = "fail"; }
// }
nRequirements = nReqChar;
if (pwd.length >= nMinPwdLen) { var nMinReqChars = 3; } else { var nMinReqChars = 4; }
if (nRequirements > nMinReqChars) { // One or more required characters exist
nScore = parseInt(nScore + (nRequirements * 2));
sRequirements = "+ " + parseInt(nRequirements * 2);
}
// $("nRequirementsBonus").innerHTML = sRequirements;
/* Determine if additional bonuses need to be applied and set image indicators accordingly */
var arrChars = [nMidChar,nRequirements];
var arrCharsIds = ["nMidChar","nRequirements"];
var arrCharsLen = arrChars.length;
// for (var c=0; c < arrCharsLen; c++) {
// var oImg = $('div_' + arrCharsIds[c]);
// var oBonus = $(arrCharsIds[c] + 'Bonus');
// $(arrCharsIds[c]).innerHTML = arrChars[c];
// if (arrCharsIds[c] == "nRequirements") { var minVal = nMinReqChars; } else { var minVal = 0; }
// if (arrChars[c] == parseInt(minVal + 1)) { oImg.className = "pass"; oBonus.parentNode.className = "pass"; }
// else if (arrChars[c] > parseInt(minVal + 1)) { oImg.className = "exceed"; oBonus.parentNode.className = "exceed"; }
// else { oImg.className = "fail"; oBonus.parentNode.className = "fail"; }
// }
/* Determine if suggested requirements have been met and set image indicators accordingly */
var arrChars = [nAlphasOnly,nNumbersOnly,nRepChar,nConsecAlphaUC,nConsecAlphaLC,nConsecNumber,nSeqAlpha,nSeqNumber,nSeqSymbol];
var arrCharsIds = ["nAlphasOnly","nNumbersOnly","nRepChar","nConsecAlphaUC","nConsecAlphaLC","nConsecNumber","nSeqAlpha","nSeqNumber","nSeqSymbol"];
var arrCharsLen = arrChars.length;
// for (var c=0; c < arrCharsLen; c++) {
// var oImg = $('div_' + arrCharsIds[c]);
// var oBonus = $(arrCharsIds[c] + 'Bonus');
// $(arrCharsIds[c]).innerHTML = arrChars[c];
// if (arrChars[c] > 0) { oImg.className = "warn"; oBonus.parentNode.className = "warn"; }
// else { oImg.className = "pass"; oBonus.parentNode.className = "pass"; }
// }
/* Determine complexity based on overall score */
if (nScore > 100) { nScore = 100; } else if (nScore < 0) { nScore = 0; }
if (nScore >= 0 && nScore < 20) { sComplexity = "Very Weak"; }
else if (nScore >= 20 && nScore < 40) { sComplexity = "Weak"; }
else if (nScore >= 40 && nScore < 60) { sComplexity = "Good"; }
else if (nScore >= 60 && nScore < 80) { sComplexity = "Strong"; }
else if (nScore >= 80 && nScore <= 100) { sComplexity = "Very Strong"; }
/* Display updated score criteria to client */
oScorebar.style.backgroundPosition = "-" + parseInt(nScore * 4) + "px 0px";
oScore.innerHTML = nScore + "%";
// oComplexity.innerHTML = sComplexity;
}
else {
/* Display default score criteria to client */
initPwdChk();
oScore.innerHTML = nScore + "%";
// oComplexity.innerHTML = sComplexity;
}
}
function togPwdMask() {
var oPwd = $("passwordPwd");
var oTxt = $("passwordTxt");
var oMask = $("mask");
if (oMask.checked) {
oPwd.value = oTxt.value;
oPwd.className = "";
oTxt.className = "hide";
}
else {
oTxt.value = oPwd.value;
oPwd.className = "hide";
oTxt.className = "";
}
}
function initPwdChk(restart) {
/* Reset all form values to their default */
var arrZeros = ["nLength","nAlphaUC","nAlphaLC","nNumber","nSymbol","nMidChar","nRequirements","nAlphasOnly","nNumbersOnly","nRepChar","nConsecAlphaUC","nConsecAlphaLC","nConsecNumber","nSeqAlpha","nSeqNumber","nSeqSymbol","nLengthBonus","nAlphaUCBonus","nAlphaLCBonus","nNumberBonus","nSymbolBonus","nMidCharBonus","nRequirementsBonus","nAlphasOnlyBonus","nNumbersOnlyBonus","nRepCharBonus","nConsecAlphaUCBonus","nConsecAlphaLCBonus","nConsecNumberBonus","nSeqAlphaBonus","nSeqNumberBonus","nSeqSymbolBonus"];
var arrPassPars = ["nAlphasOnlyBonus","nNumbersOnlyBonus","nRepCharBonus","nConsecAlphaUCBonus","nConsecAlphaLCBonus","nConsecNumberBonus","nSeqAlphaBonus","nSeqNumberBonus","nSeqSymbolBonus"];
var arrPassDivs = ["div_nAlphasOnly","div_nNumbersOnly","div_nRepChar","div_nConsecAlphaUC","div_nConsecAlphaLC","div_nConsecNumber","div_nSeqAlpha","div_nSeqNumber","div_nSeqSymbol"];
var arrFailPars = ["nLengthBonus","nAlphaUCBonus","nAlphaLCBonus","nNumberBonus","nSymbolBonus","nMidCharBonus","nRequirementsBonus"];
var arrFailDivs = ["div_nLength","div_nAlphaUC","div_nAlphaLC","div_nNumber","div_nSymbol","div_nMidChar","div_nRequirements"];
// for (var i in arrZeros) { $(arrZeros[i]).innerHTML = "0"; }
// for (var i in arrPassPars) { $(arrPassPars[i]).parentNode.className = "pass"; }
// for (var i in arrPassDivs) { $(arrPassDivs[i]).className = "pass"; }
// for (var i in arrFailPars) { $(arrFailPars[i]).parentNode.className = "fail"; }
// for (var i in arrFailDivs) { $(arrFailDivs[i]).className = "fail"; }
// $("passwordPwd").value = "";
// $("passwordTxt").value = "";
$("scorebar").style.backgroundPosition = "0 0";
if (restart) {
$("passwordPwd").className = "";
$("passwordTxt").className = "hide";
$("mask").checked = true;
}
}
addLoadEvent(function() { initPwdChk(1); });

View File

@@ -1,79 +0,0 @@
body {
background-image:linear-gradient(#0096ff 30%, white);
height:100%; width:100%;
}
#logo {
display:block;
margin-left:auto;
margin-right:auto;
margin-top:30px;
margin-bottom:40px;
text-align:center;
}
#admin {
border-style:solid;
border-width:5px;
border-color:red;
padding : 15px;
margin : 15px;
}
#admin form {
text-align : center;
}
#user {
border-style:solid;
border-width:5px;
border-color:green;
padding : 15px;
margin : 15px;
text-align:center;
}
#user input {
margin-left : 15px;
margin-right : 30px;
margin-top : 10px;
margin-bottom : 10px;
}
#select_user {
text-align : center;
}
#passwords {
border-style:solid;
border-width:5px;
border-color:grey;
padding : 15px;
margin : 15px;
}
#addon_address {
padding:10px;
text-align:center;
font-size:xx-large;
}
.hash {
width : 700px;
}
#add_new_password {
border-style:solid;
border-width:5px;
border-color:blue;
padding : 15px;
margin : 15px;
}
.error {
text-align:center;
color:red;
font-weight:bold;
font-size:xx-large;
}

View File

@@ -1,641 +0,0 @@
// parseUri 1.2.2
// (c) Steven Levithan <stevenlevithan.com>
// MIT License
// http://blog.stevenlevithan.com/archives/parseuri
function parseUri (str) {
var o = parseUri.options,
m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
uri = {},
i = 14;
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;
});
return uri;
};
parseUri.options = {
strictMode: false,
key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
q: {
name: "queryKey",
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
},
parser: {
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
}
};
if (!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, "");
};
}
// Array Remove - By John Resig (MIT Licensed)
// http://stackoverflow.com/questions/500606/javascript-array-delete-elements
Array.prototype.remove = function(from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
return this.push.apply(this, rest);
};
Element.prototype.removeAllChilds = function() {
while (this.hasChildNodes())
this.removeChild(this.childNodes[0]);
};
function generate_password()
{
// symbols 32 - 47 / 58 - 64 / 91 - 96 / 123 - 126
// numbers 48 - 57
// upper 65 - 90
// lower 97 - 122
// Give priority to letters (65 - 122 duplicated in front and end of array)
var symbols = new Array(65, 90, 97, 122, 40, 47, 48, 57, 65, 90, 97, 122, 123, 126, 65, 90, 97, 122);
field = document.getElementById("new_password");
var res = "";
while (res.length < 16)
{
a = Math.round(Math.random() * (symbols.length/2) * 2);
diff = symbols[a+1] - symbols[a];
r = Math.round(Math.random()*diff);
if (isNaN(r+symbols[a]))
continue;
res += String.fromCharCode(r + symbols[a]);
}
field.value = res;
}
function url_domain(data) {
var uri = parseUri(data)
return uri['host'];
}
// 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++)
{
c = str.charCodeAt(i).toString(16);
if (c.length == 1) c = "0" + c;
hex += c;
}
return hex;
}
function derive_mkey(user, mkey)
{
url = url_domain(document.URL) + "/" + user;
mkey = a2hex(pkdbf2(mkey, url, 1000, 256/8));
return mkey;
}
var passwords;
var current_user = "";
var current_mkey = "";
function PasswordEntry (ciphered_login="", ciphered_password="") {
this.ciphered_login = ciphered_login;
this.ciphered_password = ciphered_password;
this.unciphered = false;
this.clear_url = "";
this.clear_login = "";
this.clear_password = "";
this.masterkey = "";
this.decrypt = function(masterkey)
{
if (masterkey == this.masterkey && this.unciphered == true)
return true;
if (masterkey == "" || this.unciphered == true)
return false;
aes = new AES();
a_masterkey = aes.init(hex2a(masterkey));
login = aes.decryptLongString(hex2a(this.ciphered_login), a_masterkey);
login = login.replace(/\0*$/, "");
if (login.indexOf("@@") != 0)
{
aes.finish();
return false;
}
// Remove @@
login = login.substring(2);
infos = login.split(";");
this.clear_url = infos[0];
this.clear_login = infos[1];
this.clear_password = aes.decryptLongString(hex2a(this.ciphered_password), a_masterkey);
this.unciphered = true;
this.masterkey = masterkey;
aes.finish();
// Remove salt
this.clear_password = this.clear_password.replace(/\0*$/, "");
this.clear_password = this.clear_password.substr(0, this.clear_password.length-3);
return true;
}
this.isUnciphered = function(masterkey)
{
return (this.unciphered == true && masterkey == this.masterkey && masterkey != "")
}
this.isCiphered = function(masterkey)
{
return !(this.isUnciphered(masterkey));
}
}
function list_all_entries(user)
{
passwords = new Array();
req = new XMLHttpRequest();
req.addEventListener("load", function(evt) {
entries = this.responseText.split("\n");
if (entries[0] == "entries")
{
for(i=1; i<entries.length; i++)
{
if (entries[i] == "") continue;
entry = entries[i].split(";");
passwords.push(new PasswordEntry(entry[0], entry[1]));
}
}
}, false);
req.open("POST", document.documentURI, false);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
req.send("get_passwords=1&user=" + user);
}
function update_stats()
{
nb_ciphered_passwords = 0;
nb_unciphered_passwords = 0;
for(i=0; i<passwords.length; i++)
{
if (passwords[i].isUnciphered(current_mkey))
nb_unciphered_passwords++;
else
nb_ciphered_passwords++;
}
div = document.getElementById("nb_unciphered_passwords");
div.removeAllChilds();
text = document.createElement("b");
text.appendChild(document.createTextNode(nb_unciphered_passwords + " unciphered password(s)"));
div.appendChild(text);
div.appendChild(document.createElement("br"));
div.appendChild(document.createElement("br"));
div = document.getElementById("nb_ciphered_passwords");
div.removeAllChilds();
text = document.createElement("b");
text.appendChild(document.createTextNode(nb_ciphered_passwords + " ciphered password(s)"));
div.appendChild(text);
div.appendChild(document.createElement("br"));
div.appendChild(document.createElement("br"));
}
function change_master_key()
{
for(i=0; i<passwords.length; i++)
passwords[i].decrypt(current_mkey);
password_div = document.getElementById("passwords");
password_div.removeAllChilds();
div = document.createElement("div");
div.setAttribute("id", "nb_unciphered_passwords");
password_div.appendChild(div);
for(i=0; i<passwords.length; i++)
{
if (passwords[i].isUnciphered(current_mkey))
{
div = document.createElement("div");
div.setAttribute("id", "unciph_entry_" + i);
div.setAttribute("class", "password");
ciph_login = document.createElement("input");
ciph_login.setAttribute("name", "ciphered_login");
ciph_login.setAttribute("type", "hidden");
ciph_login.setAttribute("login", passwords[i].ciphered_login);
div.appendChild(ciph_login);
div.appendChild(document.createTextNode("URL"));
url = document.createElement("input");
url.setAttribute("type", "text");
url.setAttribute("name", "url");
url.setAttribute("value", passwords[i].clear_url);
div.appendChild(url);
div.appendChild(document.createTextNode("login"));
login = document.createElement("input");
login.setAttribute("type", "text");
login.setAttribute("name", "login");
login.setAttribute("value", passwords[i].clear_login);
div.appendChild(login);
div.appendChild(document.createTextNode("password"));
password = document.createElement("input");
password.setAttribute("type", "text");
password.setAttribute("name", "password");
password.setAttribute("value", passwords[i].clear_password);
div.appendChild(password);
delete_button = document.createElement("input");
delete_button.setAttribute("type", "button");
delete_button.setAttribute("value", "Delete");
delete_button.setAttribute("onclick", "delete_entry(\"unciph_entry_" + i + "\");");
div.appendChild(delete_button);
update_button = document.createElement("input");
update_button.setAttribute("type", "button");
update_button.setAttribute("value", "Update");
update_button.setAttribute("onclick", "update_entry(\"unciph_entry_" + i + "\");");
div.appendChild(update_button);
password_div.appendChild(div);
}
}
div = document.createElement("div");
div.setAttribute("id", "nb_ciphered_passwords");
password_div.appendChild(div);
for(i=0; i<passwords.length; i++)
{
if (passwords[i].isCiphered(current_mkey))
{
div = document.createElement("div");
div.setAttribute("id", "ciph_entry_" + i);
div.setAttribute("class", "password");
ciph_login = document.createElement("input");
ciph_login.setAttribute("name", "ciphered_login");
ciph_login.setAttribute("type", "hidden");
ciph_login.setAttribute("login", passwords[i].ciphered_login);
div.appendChild(ciph_login);
div.appendChild(document.createTextNode("URL"));
url = document.createElement("input");
url.setAttribute("class", "hash");
url.setAttribute("type", "text");
url.setAttribute("name", "URL");
url.setAttribute("value", passwords[i].ciphered_login);
div.appendChild(url);
div.appendChild(document.createTextNode("password"));
password = document.createElement("input");
password.setAttribute("class", "hash");
password.setAttribute("type", "text");
password.setAttribute("name", "password");
password.setAttribute("value", passwords[i].ciphered_password);
div.appendChild(password);
delete_button = document.createElement("input");
delete_button.setAttribute("type", "button");
delete_button.setAttribute("value", "Delete");
delete_button.setAttribute("onclick", "delete_entry(\"ciph_entry_" + i + "\");");
div.appendChild(delete_button);
password_div.appendChild(div);
}
}
update_stats();
}
function update_master_key()
{
user = select_widget.options[select_widget.selectedIndex].value;
if (user != current_user)
{
current_user = user;
document.title = "gPass : global Password - " + current_user;
list_all_entries(current_user);
addon_address = document.getElementById("addon_address");
addon_address.removeAllChilds();
addon_address.appendChild(document.createTextNode("Current addon address is : " + document.documentURI + current_user));
}
current_mkey = document.getElementById("master_key").value;
if (current_mkey != "")
current_mkey = derive_mkey(current_user, current_mkey);
change_master_key();
}
function start()
{
select_widget = document.getElementById('selected_user') ;
if (select_widget == null) return;
return update_master_key();
}
function add_password_server(user, pentry)
{
var ok = false;
req = new XMLHttpRequest();
req.addEventListener("load", function(evt) {
resp = this.responseText;
if (resp == "OK")
ok = true;
else
alert(resp);
}, false);
req.open("POST", document.documentURI, false);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
req.send("add_entry=1&user=" + user + "&login=" + pentry.ciphered_login + "&password=" + pentry.ciphered_password);
return ok;
}
function construct_pentry(user, url, password, login, mkey, derive_masterkey)
{
var ret = null;
if (url == "")
{
alert("URL is empty");
return ret;
}
if (login == "")
{
alert("Login is empty");
return ret;
}
if (password == "")
{
alert("Password is empty");
return ret;
}
if (mkey == "")
{
alert("Master key is empty");
return ret;
}
if (derive_masterkey)
mkey = derive_mkey(current_user, mkey);
for(i=0; i<passwords.length; i++)
{
p = passwords[i];
if (p.clear_url == url &&
p.clear_password == password &&
p.clear_login == login &&
p.masterkey == mkey)
{
alert("Entry already exists");
return ret;
}
}
ciphered_login = "@@" + url + ";" + login;
// Add salt
for(i=0; i<3; i++)
{
password += String.fromCharCode((Math.random() * 128)+1);
}
ciphered_password = password;
aes = new AES();
a_masterkey = aes.init(hex2a(mkey));
ciphered_login = a2hex(aes.encryptLongString(ciphered_login, a_masterkey));
ciphered_password = a2hex(aes.encryptLongString(ciphered_password, a_masterkey));
pentry = new PasswordEntry(ciphered_login, ciphered_password);
pentry.unciphered = true;
pentry.clear_url = url;
pentry.clear_login = login;
pentry.clear_password = password.substr(0, password.length-3);
pentry.masterkey = mkey;
return pentry;
}
function remove_password_server(user, login)
{
var ok = false;
req = new XMLHttpRequest();
req.addEventListener("load", function(evt) {
resp = this.responseText;
if (resp == "OK")
ok = true;
else
alert(resp);
}, false);
req.open("POST", document.documentURI, false);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
req.send("delete_entry=1&user=" + user + "&login=" + login);
return ok;
}
function add_password()
{
var url = "";
var login = "";
var password = "";
var mkey = "";
div = document.getElementById("add_new_password");
inputs = div.getElementsByTagName("input");
for(i=0; i<inputs.length; i++)
{
if (inputs[i].getAttribute("name") == "url")
url = url_domain(inputs[i].value);
else if (inputs[i].getAttribute("name") == "login")
login = inputs[i].value.trim();
else if (inputs[i].getAttribute("name") == "password")
password = inputs[i].value.trim();
else if (inputs[i].getAttribute("name") == "mkey")
mkey = inputs[i].value;
}
pentry = construct_pentry(current_user, url, password, login, mkey, true)
if (pentry == null) return;
res = add_password_server(current_user, pentry);
if (!res) return false;
current_mkey = pentry.masterkey;
passwords.push(pentry);
change_master_key();
for(i=0; i<inputs.length; i++)
{
if (inputs[i].getAttribute("type") == "text" ||
inputs[i].getAttribute("type") == "password")
inputs[i].value = "";
}
return true;
}
function delete_entry(entry_number)
{
entry = document.getElementById(entry_number);
if (entry == null) {
alert(entry_number + " not found");
return;
}
inputs = entry.getElementsByTagName("input");
var ciphered_login = null;
for(i=0; i<inputs.length; i++)
{
if (inputs[i].getAttribute("name") == "ciphered_login")
{
ciphered_login = inputs[i];
break;
}
}
if (ciphered_login == null)
{
alert("Widget not found");
return;
}
var found = -1;
for(i=0; i<passwords.length; i++)
{
if (passwords[i].ciphered_login == ciphered_login.getAttribute("login"))
{
found = i;
break;
}
}
if (found == -1)
{
alert("Password not found int database");
return;
}
if(!confirm("Are you sure want to delete this entry ?"))
return;
ok = remove_password_server(current_user, ciphered_login.getAttribute("login"));
if (!ok) return;
parent = ciphered_login.parentNode;
parent.removeAllChilds();
passwords.remove(found);
update_stats();
}
function update_entry(entry_number)
{
var url = "";
var login = "";
var password = "";
var mkey = "";
var ciphered_login;
entry = document.getElementById(entry_number);
if (entry == null) {
alert(entry_number + " not found");
return;
}
inputs = entry.getElementsByTagName("input");
var ciphered_login = null;
for(i=0; i<inputs.length; i++)
{
if (inputs[i].getAttribute("name") == "url")
url = url_domain(inputs[i].value);
else if (inputs[i].getAttribute("name") == "login")
login = inputs[i].value.trim();
else if (inputs[i].getAttribute("name") == "password")
password = inputs[i].value.trim();
else if (inputs[i].getAttribute("name") == "ciphered_login")
ciphered_login = inputs[i];
}
var found = -1;
for(i=0; i<passwords.length; i++)
{
if (passwords[i].ciphered_login == ciphered_login.getAttribute("login"))
{
found = i;
break;
}
}
if (found == -1)
{
alert("Password not found int database");
return;
}
if(!confirm("Are you sure want to update this entry ?"))
return;
pentry = construct_pentry(current_user, url, password, login, current_mkey, false);
if (pentry == null) return;
ok = remove_password_server(current_user, passwords[found].ciphered_login);
if (!ok) return;
ok = add_password_server(current_user, pentry);
if (!ok) return;
passwords[found] = pentry;
ciphered_login.setAttribute("login", pentry.ciphered_login);
alert("Entry updated");
}

View File

@@ -1,291 +0,0 @@
/*
* jsaes version 0.1 - Copyright 2006 B. Poettering
*
* This program 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 2 of the
* License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
/*
* http://point-at-infinity.org/jsaes/
*
* This is a javascript implementation of the AES block cipher. Key lengths
* of 128, 192 and 256 bits are supported.
*
* The well-functioning of the encryption/decryption routines has been
* verified for different key lengths with the test vectors given in
* FIPS-197, Appendix C.
*
* The following code example enciphers the plaintext block '00 11 22 .. EE FF'
* with the 256 bit key '00 01 02 .. 1E 1F'.
*
* AES_Init();
*
* var block = new Array(16);
* for(var i = 0; i < 16; i++)
* block[i] = 0x11 * i;
*
* var key = new Array(32);
* for(var i = 0; i < 32; i++)
* key[i] = i;
*
* AES_ExpandKey(key);
* AES_Encrypt(block, key);
*
* AES_Done();
*
* Report bugs to: jsaes AT point-at-infinity.org
*
*/
/******************************************************************************/
/*
AES_Init: initialize the tables needed at runtime. Call this function
before the (first) key expansion.
*/
function AES_Init() {
AES_Sbox_Inv = new Array(256);
for(var i = 0; i < 256; i++)
AES_Sbox_Inv[AES_Sbox[i]] = i;
AES_ShiftRowTab_Inv = new Array(16);
for(var i = 0; i < 16; i++)
AES_ShiftRowTab_Inv[AES_ShiftRowTab[i]] = i;
AES_xtime = new Array(256);
for(var i = 0; i < 128; i++) {
AES_xtime[i] = i << 1;
AES_xtime[128 + i] = (i << 1) ^ 0x1b;
}
}
/*
AES_Done: release memory reserved by AES_Init. Call this function after
the last encryption/decryption operation.
*/
function AES_Done() {
delete AES_Sbox_Inv;
delete AES_ShiftRowTab_Inv;
delete AES_xtime;
}
/*
AES_ExpandKey: expand a cipher key. Depending on the desired encryption
strength of 128, 192 or 256 bits 'key' has to be a byte array of length
16, 24 or 32, respectively. The key expansion is done "in place", meaning
that the array 'key' is modified.
*/
function AES_ExpandKey(key) {
var kl = key.length, ks, Rcon = 1;
switch (kl) {
case 16: ks = 16 * (10 + 1); break;
case 24: ks = 16 * (12 + 1); break;
case 32: ks = 16 * (14 + 1); break;
default:
alert("AES_ExpandKey: Only key lengths of 16, 24 or 32 bytes allowed!");
}
for(var i = kl; i < ks; i += 4) {
var temp = key.slice(i - 4, i);
if (i % kl == 0) {
temp = new Array(AES_Sbox[temp[1]] ^ Rcon, AES_Sbox[temp[2]],
AES_Sbox[temp[3]], AES_Sbox[temp[0]]);
if ((Rcon <<= 1) >= 256)
Rcon ^= 0x11b;
}
else if ((kl > 24) && (i % kl == 16))
temp = new Array(AES_Sbox[temp[0]], AES_Sbox[temp[1]],
AES_Sbox[temp[2]], AES_Sbox[temp[3]]);
for(var j = 0; j < 4; j++)
key[i + j] = key[i + j - kl] ^ temp[j];
}
}
/*
AES_Encrypt: encrypt the 16 byte array 'block' with the previously
expanded key 'key'.
*/
function AES_Encrypt(block, key) {
var l = key.length;
AES_AddRoundKey(block, key.slice(0, 16));
for(var i = 16; i < l - 16; i += 16) {
AES_SubBytes(block, AES_Sbox);
AES_ShiftRows(block, AES_ShiftRowTab);
AES_MixColumns(block);
AES_AddRoundKey(block, key.slice(i, i + 16));
}
AES_SubBytes(block, AES_Sbox);
AES_ShiftRows(block, AES_ShiftRowTab);
AES_AddRoundKey(block, key.slice(i, l));
}
/*
AES_Decrypt: decrypt the 16 byte array 'block' with the previously
expanded key 'key'.
*/
function AES_Decrypt(block, key) {
var l = key.length;
AES_AddRoundKey(block, key.slice(l - 16, l));
AES_ShiftRows(block, AES_ShiftRowTab_Inv);
AES_SubBytes(block, AES_Sbox_Inv);
for(var i = l - 32; i >= 16; i -= 16) {
AES_AddRoundKey(block, key.slice(i, i + 16));
AES_MixColumns_Inv(block);
AES_ShiftRows(block, AES_ShiftRowTab_Inv);
AES_SubBytes(block, AES_Sbox_Inv);
}
AES_AddRoundKey(block, key.slice(0, 16));
}
/******************************************************************************/
/* The following lookup tables and functions are for internal use only! */
AES_Sbox = new Array(99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,
118,202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,183,253,
147,38,54,63,247,204,52,165,229,241,113,216,49,21,4,199,35,195,24,150,5,154,
7,18,128,226,235,39,178,117,9,131,44,26,27,110,90,160,82,59,214,179,41,227,
47,132,83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,208,239,170,
251,67,77,51,133,69,249,2,127,80,60,159,168,81,163,64,143,146,157,56,245,
188,182,218,33,16,255,243,210,205,12,19,236,95,151,68,23,196,167,126,61,
100,93,25,115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,224,
50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,231,200,55,109,141,213,
78,169,108,86,244,234,101,122,174,8,186,120,37,46,28,166,180,198,232,221,
116,31,75,189,139,138,112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,
158,225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,223,140,161,
137,13,191,230,66,104,65,153,45,15,176,84,187,22);
AES_ShiftRowTab = new Array(0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11);
function AES_SubBytes(state, sbox) {
for(var i = 0; i < 16; i++)
state[i] = sbox[state[i]];
}
function AES_AddRoundKey(state, rkey) {
for(var i = 0; i < 16; i++)
state[i] ^= rkey[i];
}
function AES_ShiftRows(state, shifttab) {
var h = new Array().concat(state);
for(var i = 0; i < 16; i++)
state[i] = h[shifttab[i]];
}
function AES_MixColumns(state) {
for(var i = 0; i < 16; i += 4) {
var s0 = state[i + 0], s1 = state[i + 1];
var s2 = state[i + 2], s3 = state[i + 3];
var h = s0 ^ s1 ^ s2 ^ s3;
state[i + 0] ^= h ^ AES_xtime[s0 ^ s1];
state[i + 1] ^= h ^ AES_xtime[s1 ^ s2];
state[i + 2] ^= h ^ AES_xtime[s2 ^ s3];
state[i + 3] ^= h ^ AES_xtime[s3 ^ s0];
}
}
function AES_MixColumns_Inv(state) {
for(var i = 0; i < 16; i += 4) {
var s0 = state[i + 0], s1 = state[i + 1];
var s2 = state[i + 2], s3 = state[i + 3];
var h = s0 ^ s1 ^ s2 ^ s3;
var xh = AES_xtime[h];
var h1 = AES_xtime[AES_xtime[xh ^ s0 ^ s2]] ^ h;
var h2 = AES_xtime[AES_xtime[xh ^ s1 ^ s3]] ^ h;
state[i + 0] ^= h1 ^ AES_xtime[s0 ^ s1];
state[i + 1] ^= h2 ^ AES_xtime[s1 ^ s2];
state[i + 2] ^= h1 ^ AES_xtime[s2 ^ s3];
state[i + 3] ^= h2 ^ AES_xtime[s3 ^ s0];
}
}
function bin2String (array) {
var result = "";
for (var i = 0; i < array.length; i++) {
result += String.fromCharCode(parseInt(array[i], 2));
}
return result;
}
function string2Bin (str) {
var result = [];
for (var i = 0; i < str.length; i++) {
result.push(str.charCodeAt(i));
}
while ((result.length % 16))
result.push(0);
return result;
}
function bin2String (array) {
return String.fromCharCode.apply(String, array);
}
// http://osama-oransa.blogspot.fr/2012/03/using-aes-encrypting-in-java-script.html
function AES(a) {
this.init = function (myKey){
AES_Init();
var key = string2Bin(myKey);
AES_ExpandKey(key);
return key;
}
this.encrypt = function ( inputStr,key ) {
var block = string2Bin(inputStr);
AES_Encrypt(block, key);
var data=bin2String(block);
return data;
}
this.decrypt = function ( inputStr,key ) {
block = string2Bin(inputStr);
AES_Decrypt(block, key);
var data=bin2String(block);
return data;
}
this.encryptLongString = function( myString,key ) {
if(myString.length>16){
var data='';
for(var i=0;i<myString.length;i=i+16){
data+=this.encrypt(myString.substr(i,16),key);
}
return data;
}else{
return this.encrypt(myString,key);
}
}
this.decryptLongString = function ( myString,key ) {
if(myString.length>16){
var data='';
for(var i=0;i<myString.length;i=i+16){
data+=this.decrypt(myString.substr(i,16),key);
}
return data;
}else{
return this.decrypt(myString,key);
}
}
this.finish = function(){
AES_Done();
}
}

View File

@@ -1,261 +0,0 @@
/*
* A JavaScript implementation of the SHA256 hash function.
*
* FILE: sha256.js
* VERSION: 0.8
* AUTHOR: Christoph Bichlmeier <informatik@zombiearena.de>
*
* NOTE: This version is not tested thoroughly!
*
* Copyright (c) 2003, Christoph Bichlmeier
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* ======================================================================
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* SHA256 logical functions */
function rotateRight(n,x) {
return ((x >>> n) | (x << (32 - n)));
}
function choice(x,y,z) {
return ((x & y) ^ (~x & z));
}
function majority(x,y,z) {
return ((x & y) ^ (x & z) ^ (y & z));
}
function sha256_Sigma0(x) {
return (rotateRight(2, x) ^ rotateRight(13, x) ^ rotateRight(22, x));
}
function sha256_Sigma1(x) {
return (rotateRight(6, x) ^ rotateRight(11, x) ^ rotateRight(25, x));
}
function sha256_sigma0(x) {
return (rotateRight(7, x) ^ rotateRight(18, x) ^ (x >>> 3));
}
function sha256_sigma1(x) {
return (rotateRight(17, x) ^ rotateRight(19, x) ^ (x >>> 10));
}
function sha256_expand(W, j) {
return (W[j&0x0f] += sha256_sigma1(W[(j+14)&0x0f]) + W[(j+9)&0x0f] +
sha256_sigma0(W[(j+1)&0x0f]));
}
/* Hash constant words K: */
var K256 = new Array(
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
);
/* global arrays */
var ihash, count, buffer;
var sha256_hex_digits = "0123456789abcdef";
/* Add 32-bit integers with 16-bit operations (bug in some JS-interpreters:
overflow) */
function safe_add(x, y)
{
var lsw = (x & 0xffff) + (y & 0xffff);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xffff);
}
/* Initialise the SHA256 computation */
function sha256_init() {
ihash = new Array(8);
count = new Array(2);
buffer = new Array(64);
count[0] = count[1] = 0;
ihash[0] = 0x6a09e667;
ihash[1] = 0xbb67ae85;
ihash[2] = 0x3c6ef372;
ihash[3] = 0xa54ff53a;
ihash[4] = 0x510e527f;
ihash[5] = 0x9b05688c;
ihash[6] = 0x1f83d9ab;
ihash[7] = 0x5be0cd19;
}
/* Transform a 512-bit message block */
function sha256_transform() {
var a, b, c, d, e, f, g, h, T1, T2;
var W = new Array(16);
/* Initialize registers with the previous intermediate value */
a = ihash[0];
b = ihash[1];
c = ihash[2];
d = ihash[3];
e = ihash[4];
f = ihash[5];
g = ihash[6];
h = ihash[7];
/* make 32-bit words */
for(var i=0; i<16; i++)
W[i] = ((buffer[(i<<2)+3]) | (buffer[(i<<2)+2] << 8) | (buffer[(i<<2)+1]
<< 16) | (buffer[i<<2] << 24));
for(var j=0; j<64; j++) {
T1 = h + sha256_Sigma1(e) + choice(e, f, g) + K256[j];
if(j < 16) T1 += W[j];
else T1 += sha256_expand(W, j);
T2 = sha256_Sigma0(a) + majority(a, b, c);
h = g;
g = f;
f = e;
e = safe_add(d, T1);
d = c;
c = b;
b = a;
a = safe_add(T1, T2);
}
/* Compute the current intermediate hash value */
ihash[0] += a;
ihash[1] += b;
ihash[2] += c;
ihash[3] += d;
ihash[4] += e;
ihash[5] += f;
ihash[6] += g;
ihash[7] += h;
}
/* Read the next chunk of data and update the SHA256 computation */
function sha256_update(data, inputLen) {
var i, index, curpos = 0;
/* Compute number of bytes mod 64 */
index = ((count[0] >> 3) & 0x3f);
var remainder = (inputLen & 0x3f);
/* Update number of bits */
if ((count[0] += (inputLen << 3)) < (inputLen << 3)) count[1]++;
count[1] += (inputLen >> 29);
/* Transform as many times as possible */
for(i=0; i+63<inputLen; i+=64) {
for(var j=index; j<64; j++)
buffer[j] = data.charCodeAt(curpos++);
sha256_transform();
index = 0;
}
/* Buffer remaining input */
for(var j=0; j<remainder; j++)
buffer[j] = data.charCodeAt(curpos++);
}
/* Finish the computation by operations such as padding */
function sha256_final() {
var index = ((count[0] >> 3) & 0x3f);
buffer[index++] = 0x80;
if(index <= 56) {
for(var i=index; i<56; i++)
buffer[i] = 0;
} else {
for(var i=index; i<64; i++)
buffer[i] = 0;
sha256_transform();
for(var i=0; i<56; i++)
buffer[i] = 0;
}
buffer[56] = (count[1] >>> 24) & 0xff;
buffer[57] = (count[1] >>> 16) & 0xff;
buffer[58] = (count[1] >>> 8) & 0xff;
buffer[59] = count[1] & 0xff;
buffer[60] = (count[0] >>> 24) & 0xff;
buffer[61] = (count[0] >>> 16) & 0xff;
buffer[62] = (count[0] >>> 8) & 0xff;
buffer[63] = count[0] & 0xff;
sha256_transform();
}
/* Split the internal hash values into an array of bytes */
function sha256_encode_bytes() {
var j=0;
var output = new Array(32);
for(var i=0; i<8; i++) {
output[j++] = ((ihash[i] >>> 24) & 0xff);
output[j++] = ((ihash[i] >>> 16) & 0xff);
output[j++] = ((ihash[i] >>> 8) & 0xff);
output[j++] = (ihash[i] & 0xff);
}
return output;
}
/* Get the internal hash as a hex string */
function sha256_encode_hex() {
var output = new String();
for(var i=0; i<8; i++) {
for(var j=28; j>=0; j-=4)
output += sha256_hex_digits.charAt((ihash[i] >>> j) & 0x0f);
}
return output;
}
/* Get the internal hash as string */
function sha256_encode() {
var output = new String();
for(var i=0; i<8; i++) {
for(var j=3; j>=0; j--)
output += String.fromCharCode((ihash[i] >>> j*8) & 0xff);
}
return output;
}
/* Main function: returns a hex string representing the SHA256 value of the
given data */
function digest256 (data) {
sha256_init();
sha256_update(data, data.length);
sha256_final();
return sha256_encode();
// return sha256_encode_hex();
}
/* test if the JS-interpreter is working properly */
function sha256_self_test()
{
return sha256_digest("message digest") ==
"f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650";
}

201
server/tests/test.js Normal file
View File

@@ -0,0 +1,201 @@
server_url = "https://gpass.soutade.fr";
current_user = "test-v7";
pbkdf2_level = 1000;
CRYPTO_V1_COMPATIBLE = 1;
use_shadow_logins = false;
/*
Must contains :
URL login password masterkey
-----------------------------------------------------------------
v7 format
test test test test
test2 test2 test2 test2 (+shadow login)
test16 test16 testtesttesttest test16
test17 test17 testtesttesttestt test17
v8 format
testv8 testv8 testv8 testv8
testv8_2 testv8_2 testv8_2 testv8_2 (+shadow login)
testv8_16 testv8_16 testtesttesttest testv8_16
testv8_17 testv8_17 testtesttesttestt testv8_17
We assume shadow logins are enabled in server side
*/
function alert(a) {}
function nb_unciphered_passwords(passwords)
{
var nb_unciphered = 0;
for(i=0;i<passwords.length; i++)
{
if (passwords[i].unciphered)
nb_unciphered++;
}
return nb_unciphered;
}
function find_password(passwords, login)
{
for(i=0; i<passwords.length; i++)
{
if (passwords[i].clear_login == login)
return passwords[i];
}
return null;
}
async function decrypt_passwords(passwords, masterkey)
{
var nb_unciphered = 0;
for(nb_unciphered = 0, i=0;i<passwords.length; i++)
{
res = await passwords[i].decrypt(current_mkey);
if (res == true)
nb_unciphered++;
}
return nb_unciphered;
}
QUnit.test( "Testbench", async function( assert ) {
assert.ok( passwords == null, "Initial passwords null" );
list_all_entries(current_user);
assert.ok( passwords != null, "Retrieved passwords" );
assert.equal( passwords.length, 8, "8 passwords retrieved" );
var shadow_logins = 0;
for(i=0;i<passwords.length; i++)
if (passwords[i].shadow_login.length)
shadow_logins++;
assert.ok( shadow_logins == 2, "2 shadow logins" );
current_mkey = derive_mkey(current_user, "test");
var nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal( nb_unciphered, 1, "test #1 decrypted" );
await get_ciphered_credentials(current_mkey);
res = nb_unciphered_passwords(passwords);
assert.equal(res, 1, "No more passwords unciphered with shadow logins");
current_mkey = derive_mkey(current_user, "test2");
// Get ciphered credentials for "test2"
await get_ciphered_credentials(current_mkey);
res = nb_unciphered_passwords(passwords);
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Shadow logins OK");
current_mkey = derive_mkey(current_user, "test16");
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Test16 OK");
current_mkey = derive_mkey(current_user, "test17");
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Test17 OK");
// V8
current_mkey = derive_mkey(current_user, "testv8");
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Testv8 OK");
current_mkey = derive_mkey(current_user, "testv8_2");
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 0, "Testv8_2 without shadow login");
await get_ciphered_credentials(current_mkey);
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Testv8_2 OK");
current_mkey = derive_mkey(current_user, "testv8_16");
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Testv8_16 OK");
current_mkey = derive_mkey(current_user, "testv8_17");
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Testv8_17 OK");
nb_unciphered = nb_unciphered_passwords(passwords);
assert.equal(nb_unciphered, 8, "All passwords unciphered");
password = find_password(passwords, "testv8");
var ok = remove_password_server(current_user, passwords[i].ciphered_login,
passwords[i].access_token);
assert.equal(ok, true, "Remove OK");
alert(passwords[i].ciphered_login);
ok = remove_password_server(current_user, passwords[i].ciphered_login,
passwords[i].access_token);
assert.equal(ok, true, "Double remove OK");
password = find_password(passwords, "testv8_2");
ok = remove_password_server(current_user, passwords[i].ciphered_login,
"");
assert.equal(ok, false, "Remove without access token OK");
ok = remove_password_server(current_user, passwords[i].ciphered_login,
"AAAAAAAAAAAAAAAA");
assert.equal(ok, false, "Remove Bad access token");
res = await construct_pentry(current_user, "testv8_new", "testv8_new", "testv8_new", "testv8_new", true).then(
function (pentry) {
if (pentry == null) return false;
return add_password_server(current_user, pentry);
});
assert.equal(res, false, "Add without access token OK");
use_shadow_logins = true;
res = await construct_pentry(current_user, "testv8_new", "testv8_new", "testv8_new", "testv8_new", true).then(
function (pentry) {
if (pentry == null) return false;
return add_password_server(current_user, pentry);
});
assert.equal(res, true, "Add with access token OK");
res = add_password_server(current_user, passwords[passwords.length-1]);
assert.equal(res, false, "Double add OK");
res = await construct_pentry(current_user, "testv8_new2", "testv8_new2", "testv8_new2", "testv8_new2", true).then(
function (pentry) {
if (pentry == null) return false;
pentry.shadow_login = "AAA";
return add_password_server(current_user, pentry);
});
assert.equal(res, false, "Add with truncated shadow login OK");
password = find_password(passwords, "test16");
ok = remove_password_server(current_user, password.ciphered_login,
password.access_token);
assert.equal(ok, true, "Remove v7");
password = find_password(passwords, "test16");
ok = remove_password_server(current_user, password.ciphered_login,
"AAAAAAAAAAAAAAAA");
assert.equal(ok, true, "Remove v7 bad access token");
});
QUnit.test( "Updated database", async function( assert ) {
passwords = null;
list_all_entries(current_user);
assert.ok( passwords != null, "Passed!" );
assert.equal( passwords.length, 7, "7 passwords retrieved" );
current_mkey = derive_mkey(current_user, "testv8");
var nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal( nb_unciphered, 0, "Password removed" );
current_mkey = derive_mkey(current_user, "testv8_new");
await get_ciphered_credentials(current_mkey);
nb_unciphered = await decrypt_passwords(passwords, current_user);
assert.equal(nb_unciphered, 1, "Password added");
});

17
server/tests/tests.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>QUnit Example</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.3.0.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="https://code.jquery.com/qunit/qunit-2.3.0.js"></script>
<script src="../resources/misc.js"></script>
<script src="../resources/gpass.js"></script>
<script src="test.js"></script>
</body>
</html>

Binary file not shown.

Binary file not shown.

View File

@@ -1,3 +1,4 @@
<?php
/*
Copyright (C) 2013 Grégory Soutadé
@@ -17,30 +18,6 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
function hmac256(key, message) {
var ipad = "";
var opad = "";
include "../../_user";
if (key.length > 512/8)
{
key = digest256(key);
}
for(i=0; i<512/8; i++)
{
if (i >= key.length)
{
ipad += String.fromCharCode(0x36);
opad += String.fromCharCode(0x5c);
}
else
{
ipad += String.fromCharCode(key.charCodeAt(i) ^ 0x36);
opad += String.fromCharCode(key.charCodeAt(i) ^ 0x5c);
}
}
result = digest256(opad + digest256(ipad + message));
return result;
}
?>