New protocol v3 : include pkdbf2 level
Remove hashtable from firefox addon Rework firefox addon Add pkdbf2_level as a preference (hidden)
This commit is contained in:
parent
1ff4a87beb
commit
84eaf0c6a1
|
@ -42,10 +42,11 @@ 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.
|
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.
|
Configuration parameters are in conf.php
|
||||||
|
|
||||||
A demonstration server is available [here](http://gpass-demo.soutade.fr). It's the default server of XPI package (user demo).
|
A demonstration server is available [here](http://gpass-demo.soutade.fr). It's the default server of XPI package (user demo).
|
||||||
|
|
||||||
|
Warning The master key derivation is partially based on account URL. So it's linked to your current server information. Currently there is no simple way to export/import a full gPass database.
|
||||||
|
|
||||||
Client
|
Client
|
||||||
------
|
------
|
||||||
|
|
|
@ -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;
|
|
|
@ -20,14 +20,14 @@
|
||||||
var {Cc, Ci} = require("chrome");
|
var {Cc, Ci} = require("chrome");
|
||||||
var notifications = require("sdk/notifications");
|
var notifications = require("sdk/notifications");
|
||||||
|
|
||||||
// http://www.timdown.co.uk/jshashtable/
|
|
||||||
var Hashtable = require("jshashtable-3.0").Hashtable;
|
|
||||||
var pkdbf2 = require("pkdbf2").pkdbf2;
|
var pkdbf2 = require("pkdbf2").pkdbf2;
|
||||||
var aes = require("jsaes").aes;
|
var aes = require("jsaes").aes;
|
||||||
var parseURI = require("parseuri").parseURI;
|
var parseURI = require("parseuri").parseURI;
|
||||||
var prefSet = require("simple-prefs");
|
var prefSet = require("sdk/simple-prefs");
|
||||||
var DEBUG = false;
|
var DEBUG = false;
|
||||||
|
var pkdbf2_level = prefSet.prefs.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
|
// http://stackoverflow.com/questions/3745666/how-to-convert-from-hex-to-ascii-in-javascript
|
||||||
function hex2a(hex) {
|
function hex2a(hex) {
|
||||||
|
@ -54,6 +54,185 @@ function debug(s)
|
||||||
console.log(s);
|
console.log(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generate_request(domain, login, mkey)
|
||||||
|
{
|
||||||
|
v = "@@" + domain + ";" + login;
|
||||||
|
debug("will encrypt " + v);
|
||||||
|
debug("with " + a2hex(mkey));
|
||||||
|
enc = aes.encryptLongString(v, aes.init(mkey));
|
||||||
|
aes.finish();
|
||||||
|
debug("res " + enc);
|
||||||
|
|
||||||
|
return enc;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ask_server(field, logins, domain, wdomain, mkey, salt)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Need to do a synchronous request
|
||||||
|
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) {
|
||||||
|
ciphered_password = "";
|
||||||
|
server_pkdbf2_level = 0;
|
||||||
|
server_version = 0;
|
||||||
|
|
||||||
|
r = this.responseText.split("\n");
|
||||||
|
debug("resp " + r);
|
||||||
|
|
||||||
|
for(a=0; a<r.length; a++)
|
||||||
|
{
|
||||||
|
debug("Analyse " + r[a]);
|
||||||
|
|
||||||
|
params = r[a].split("=");
|
||||||
|
if (params.length != 2 && params[0] != "<end>")
|
||||||
|
{
|
||||||
|
notifications.notify({
|
||||||
|
title: "gPasss",
|
||||||
|
text: "Error : It seems that it's not a gPass server",
|
||||||
|
data: this.responseText,
|
||||||
|
});
|
||||||
|
ret = SERVER.FAILED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(params[0])
|
||||||
|
{
|
||||||
|
case "protocol":
|
||||||
|
debug("protocol : " + params[1]);
|
||||||
|
|
||||||
|
if (params[1].indexOf("gpass-") != 0)
|
||||||
|
{
|
||||||
|
notifications.notify({
|
||||||
|
title: "gPasss",
|
||||||
|
text: "Error : It seems that it's not a gPass server",
|
||||||
|
data: this.responseText,
|
||||||
|
});
|
||||||
|
ret = SERVER.FAILED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
server_protocol_version = params[1].match(/\d+/)[0];
|
||||||
|
|
||||||
|
if (server_protocol_version > protocol_version)
|
||||||
|
{
|
||||||
|
notifications.notify({
|
||||||
|
title: "gPasss",
|
||||||
|
text: "Protocol version not supported, please upgrade your addon",
|
||||||
|
data: "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)
|
||||||
|
{
|
||||||
|
debug("New pkdbf2 level " + server_pkdbf2_level);
|
||||||
|
pkdbf2_level = server_pkdbf2_level;
|
||||||
|
prefSet.prefs.pkdbf2_level = server_pkdbf2_level;
|
||||||
|
ret = SERVER.RESTART_REQUEST;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "<end>":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
debug("Unknown command " + params[0]);
|
||||||
|
|
||||||
|
notifications.notify({
|
||||||
|
title: "gPasss",
|
||||||
|
text: "Error : It seems that it's not a gPass server",
|
||||||
|
data: 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;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
debug("No password found");
|
||||||
|
|
||||||
|
ret = SERVER.FAILED;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
function wildcard_domain(domain)
|
function wildcard_domain(domain)
|
||||||
{
|
{
|
||||||
parts = domain.split(".");
|
parts = domain.split(".");
|
||||||
|
@ -78,17 +257,19 @@ function on_sumbit(e)
|
||||||
{
|
{
|
||||||
var form = this;
|
var form = this;
|
||||||
var fields = form.getElementsByTagName("input");
|
var fields = form.getElementsByTagName("input");
|
||||||
var my_map = new Hashtable();
|
|
||||||
|
|
||||||
domain = parseURI.parseUri(form.ownerDocument.baseURI);
|
domain = parseURI.parseUri(form.ownerDocument.baseURI);
|
||||||
domain = domain["host"];
|
domain = domain["host"];
|
||||||
wdomain = wildcard_domain(domain);
|
wdomain = wildcard_domain(domain);
|
||||||
|
|
||||||
salt = parseURI.parseUri(prefSet.prefs["account_url"]);
|
salt = parseURI.parseUri(prefSet.prefs.account_url);
|
||||||
salt = salt["host"] + salt["path"];
|
salt = salt["host"] + salt["path"];
|
||||||
|
|
||||||
debug("salt " + salt);
|
debug("salt " + salt);
|
||||||
|
|
||||||
|
user = null;
|
||||||
|
all_logins = new Array;
|
||||||
|
|
||||||
// Get all <input type="text"> && <input type="email">
|
// Get all <input type="text"> && <input type="email">
|
||||||
for (i=0; i<fields.length; i++)
|
for (i=0; i<fields.length; i++)
|
||||||
{
|
{
|
||||||
|
@ -96,10 +277,23 @@ function on_sumbit(e)
|
||||||
if (field.getAttribute("type") == "text" || field.getAttribute("type") == "email")
|
if (field.getAttribute("type") == "text" || field.getAttribute("type") == "email")
|
||||||
{
|
{
|
||||||
if (field.hasAttribute("name") && field.value != "")
|
if (field.hasAttribute("name") && field.value != "")
|
||||||
my_map.put(field.getAttribute("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;
|
||||||
|
all_logins.push(field.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user != null)
|
||||||
|
logins = new Array(user);
|
||||||
|
else
|
||||||
|
logins = all_logins;
|
||||||
|
|
||||||
// Look for <input type="password" value="@@...">
|
// Look for <input type="password" value="@@...">
|
||||||
for (i=0; i<fields.length; i++)
|
for (i=0; i<fields.length; i++)
|
||||||
{
|
{
|
||||||
|
@ -113,128 +307,30 @@ function on_sumbit(e)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
mkey = password.substring(2);
|
mkey = password.substring(2);
|
||||||
mkey = pkdbf2.pkdbf2(mkey, salt, 1000, 256/8);
|
|
||||||
|
|
||||||
user = null;
|
var ret = ask_server(field, logins, domain, wdomain, mkey, salt);
|
||||||
// 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");
|
|
||||||
|
|
||||||
// If no one found, use all
|
switch(ret)
|
||||||
logins = (user != null) ? new Array(user) : my_map.values();
|
|
||||||
|
|
||||||
keys = "";
|
|
||||||
for(a=0, b=logins.length; a<logins.length; a++)
|
|
||||||
{
|
{
|
||||||
v = "@@" + domain + ";" + logins[a];
|
case SERVER.OK:
|
||||||
debug("will encrypt " + v);
|
return true;
|
||||||
debug("with " + a2hex(mkey));
|
case SERVER.FAILED:
|
||||||
enc = aes.encryptLongString(v, aes.init(mkey));
|
if (logins !== all_logins)
|
||||||
aes.finish();
|
|
||||||
debug("res " + enc);
|
|
||||||
|
|
||||||
keys += (keys.length != 0) ? "&" : "";
|
|
||||||
keys += "k" + a + "=" + a2hex(enc);
|
|
||||||
|
|
||||||
if (wdomain != "")
|
|
||||||
{
|
{
|
||||||
v = "@@" + wdomain + ";" + logins[a];
|
ret = ask_server(field, all_logins, domain, wdomain, mkey, salt);
|
||||||
debug("will encrypt " + v);
|
if (ret == SERVER.OK)
|
||||||
debug("with " + a2hex(mkey));
|
return true;
|
||||||
enc = aes.encryptLongString(v, aes.init(mkey));
|
|
||||||
aes.finish();
|
|
||||||
debug("res " + enc);
|
|
||||||
|
|
||||||
keys += (keys.length != 0) ? "&" : "";
|
|
||||||
keys += "k" + (b++) + "=" + 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)
|
|
||||||
{
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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();
|
e.preventDefault();
|
||||||
return ret;
|
break;
|
||||||
|
case SERVER.RESTART_REQUEST:
|
||||||
|
i = -1; // Restart loop
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function document_loaded(event)
|
function document_loaded(event)
|
||||||
|
@ -273,12 +369,11 @@ observerService.addObserver(httpRequestObserver, "content-document-global-create
|
||||||
|
|
||||||
function self_test()
|
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))) !=
|
if((res = a2hex(pkdbf2.pkdbf2("password", "salt", 4096, 256/8))) !=
|
||||||
"c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a")
|
"c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a")
|
||||||
console.log("PKDBF2 failed " + res);
|
console.log("PKDBF2 failed " + res);
|
||||||
else
|
else
|
||||||
console.log("All is OK ! " + mkey);
|
console.log("All is OK ! ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// self_test();
|
||||||
|
|
|
@ -5,12 +5,22 @@
|
||||||
"description": "gPass : global password manager",
|
"description": "gPass : global password manager",
|
||||||
"author": "Grégory Soutadé",
|
"author": "Grégory Soutadé",
|
||||||
"license": "GNU GPL v3",
|
"license": "GNU GPL v3",
|
||||||
"version": "0.2",
|
"version": "0.3",
|
||||||
"preferences": [{
|
"preferences": [
|
||||||
|
{
|
||||||
"name": "account_url",
|
"name": "account_url",
|
||||||
"title": "Account URL",
|
"title": "Account URL",
|
||||||
"description": "URL of your gPass account",
|
"description": "URL of your gPass account",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": "http://gpass-demo.soutade.fr/demo"
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2013 Grégory Soutadé
|
Copyright (C) 2013-2014 Grégory Soutadé
|
||||||
|
|
||||||
This file is part of gPass.
|
This file is part of gPass.
|
||||||
|
|
||||||
|
@ -18,6 +18,8 @@
|
||||||
along with gPass. If not, see <http://www.gnu.org/licenses/>.
|
along with gPass. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
include("conf.php");
|
||||||
|
|
||||||
function load_database()
|
function load_database()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
@ -31,7 +33,7 @@ function load_database()
|
||||||
return $db;
|
return $db;
|
||||||
}
|
}
|
||||||
|
|
||||||
$PROTOCOL_VERSION = 2;
|
$PROTOCOL_VERSION = 3;
|
||||||
|
|
||||||
$db = load_database();
|
$db = load_database();
|
||||||
|
|
||||||
|
@ -40,6 +42,8 @@ $res = "";
|
||||||
$statement = $db->prepare("SELECT password FROM gpass WHERE login=:login");
|
$statement = $db->prepare("SELECT password FROM gpass WHERE login=:login");
|
||||||
|
|
||||||
echo "protocol=gpass-$PROTOCOL_VERSION\n";
|
echo "protocol=gpass-$PROTOCOL_VERSION\n";
|
||||||
|
if ($PKDBF2_LEVEL != 1000)
|
||||||
|
echo "pkdbf2_level=$PKDBF2_LEVEL\n";
|
||||||
|
|
||||||
for ($i=0; isset($_POST["k$i"]); $i++)
|
for ($i=0; isset($_POST["k$i"]); $i++)
|
||||||
{
|
{
|
||||||
|
|
41
server/conf.php
Normal file
41
server/conf.php
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013-2014 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
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 PKDBF2 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 !
|
||||||
|
*/
|
||||||
|
$PKDBF2_LEVEL=1000;
|
||||||
|
?>
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2013 Grégory Soutadé
|
Copyright (C) 2013-2014 Grégory Soutadé
|
||||||
|
|
||||||
This file is part of gPass.
|
This file is part of gPass.
|
||||||
|
|
||||||
|
@ -20,10 +20,10 @@
|
||||||
|
|
||||||
include('functions.php');
|
include('functions.php');
|
||||||
|
|
||||||
|
include('conf.php');
|
||||||
|
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
$VIEW_CIPHERED_PASSWORDS=true;
|
|
||||||
$ADMIN_MODE=true;
|
|
||||||
$user = "";
|
$user = "";
|
||||||
|
|
||||||
if ($ADMIN_MODE && isset($_POST['create_user']))
|
if ($ADMIN_MODE && isset($_POST['create_user']))
|
||||||
|
@ -51,6 +51,11 @@ else
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
|
||||||
<link rel="stylesheet" type="text/css" href="ressources/gpass.css" />
|
<link rel="stylesheet" type="text/css" href="ressources/gpass.css" />
|
||||||
|
<script language="javascript">
|
||||||
|
<?php
|
||||||
|
echo "pkdbf2_level=$PKDBF2_LEVEL;\n";
|
||||||
|
?>
|
||||||
|
</script>
|
||||||
<script src="ressources/jsaes.js"></script>
|
<script src="ressources/jsaes.js"></script>
|
||||||
<script src="ressources/jssha256.js"></script>
|
<script src="ressources/jssha256.js"></script>
|
||||||
<script src="ressources/hmac.js"></script>
|
<script src="ressources/hmac.js"></script>
|
||||||
|
|
|
@ -103,7 +103,7 @@ function a2hex(str) {
|
||||||
function derive_mkey(user, mkey)
|
function derive_mkey(user, mkey)
|
||||||
{
|
{
|
||||||
url = url_domain(document.URL) + "/" + user;
|
url = url_domain(document.URL) + "/" + user;
|
||||||
mkey = a2hex(pkdbf2(mkey, url, 1000, 256/8));
|
mkey = a2hex(pkdbf2(mkey, url, pkdbf2_level, 256/8));
|
||||||
return mkey;
|
return mkey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user