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:
		| @@ -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. | ||||
|  | ||||
| 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). | ||||
|  | ||||
| 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 | ||||
| ------ | ||||
|   | ||||
| @@ -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 notifications = require("sdk/notifications"); | ||||
|  | ||||
| // 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 prefSet = require("sdk/simple-prefs"); | ||||
| 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 | ||||
| function hex2a(hex) { | ||||
| @@ -54,6 +54,185 @@ function debug(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) | ||||
| { | ||||
|     parts = domain.split("."); | ||||
| @@ -78,17 +257,19 @@ function on_sumbit(e) | ||||
| { | ||||
|     var form = this; | ||||
|     var fields = form.getElementsByTagName("input"); | ||||
|     var my_map = new Hashtable(); | ||||
|  | ||||
|     domain = parseURI.parseUri(form.ownerDocument.baseURI); | ||||
|     domain = domain["host"]; | ||||
|     wdomain = wildcard_domain(domain); | ||||
|  | ||||
|     salt = parseURI.parseUri(prefSet.prefs["account_url"]); | ||||
|     salt = parseURI.parseUri(prefSet.prefs.account_url); | ||||
|     salt = salt["host"] + salt["path"]; | ||||
|  | ||||
|     debug("salt " + salt); | ||||
|  | ||||
|     user = null; | ||||
|     all_logins = new Array; | ||||
|  | ||||
|     // Get all <input type="text"> && <input type="email"> | ||||
|     for (i=0; i<fields.length; i++) | ||||
|     { | ||||
| @@ -96,9 +277,22 @@ function on_sumbit(e) | ||||
| 	if (field.getAttribute("type") == "text" || field.getAttribute("type") == "email") | ||||
| 	{ | ||||
| 	    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="@@..."> | ||||
|     for (i=0; i<fields.length; i++) | ||||
| @@ -113,128 +307,30 @@ function on_sumbit(e) | ||||
| 		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"); | ||||
| 	    var ret = ask_server(field, logins, domain, wdomain, mkey, salt); | ||||
|  | ||||
| 	    // If no one found, use all | ||||
| 	    logins = (user != null) ? new Array(user) : my_map.values(); | ||||
|  | ||||
| 	    keys = ""; | ||||
| 	    for(a=0, b=logins.length; 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); | ||||
|  | ||||
| 		if (wdomain != "") | ||||
| 	    case SERVER.OK: | ||||
| 		return true; | ||||
| 	    case SERVER.FAILED: | ||||
| 		if (logins !== all_logins) | ||||
| 		{ | ||||
| 		    v = "@@" + wdomain + ";" + 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" + (b++) + "=" + a2hex(enc); | ||||
| 		    ret = ask_server(field, all_logins, domain, wdomain, mkey, salt); | ||||
| 		    if (ret == SERVER.OK) | ||||
| 			return true; | ||||
| 		} | ||||
| 	    } | ||||
|  | ||||
| 	    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(); | ||||
| 		return ret; | ||||
| 		break; | ||||
| 	    case SERVER.RESTART_REQUEST: | ||||
| 		i = -1; // Restart loop | ||||
| 		break; | ||||
| 	    } | ||||
| 	} | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function document_loaded(event) | ||||
| @@ -273,12 +369,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(); | ||||
|   | ||||
| @@ -5,12 +5,22 @@ | ||||
|   "description": "gPass : global password manager", | ||||
|   "author": "Grégory Soutadé", | ||||
|   "license": "GNU GPL v3", | ||||
|   "version": "0.2", | ||||
|     "preferences": [{ | ||||
|   "version": "0.3", | ||||
|     "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 | ||||
|     } | ||||
|     ] | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <?php | ||||
| /* | ||||
|   Copyright (C) 2013 Grégory Soutadé | ||||
|   Copyright (C) 2013-2014 Grégory Soutadé | ||||
|    | ||||
|   This file is part of gPass. | ||||
|    | ||||
| @@ -18,6 +18,8 @@ | ||||
|   along with gPass.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
|  | ||||
| include("conf.php"); | ||||
|  | ||||
| function load_database() | ||||
| { | ||||
|     try { | ||||
| @@ -31,7 +33,7 @@ function load_database() | ||||
|     return $db; | ||||
| } | ||||
|  | ||||
| $PROTOCOL_VERSION = 2; | ||||
| $PROTOCOL_VERSION = 3; | ||||
|  | ||||
| $db = load_database(); | ||||
|  | ||||
| @@ -40,6 +42,8 @@ $res = ""; | ||||
| $statement = $db->prepare("SELECT password FROM gpass WHERE login=:login"); | ||||
|  | ||||
| echo "protocol=gpass-$PROTOCOL_VERSION\n"; | ||||
| if ($PKDBF2_LEVEL != 1000) | ||||
|     echo "pkdbf2_level=$PKDBF2_LEVEL\n"; | ||||
|  | ||||
| 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 | ||||
| /* | ||||
|   Copyright (C) 2013 Grégory Soutadé | ||||
|   Copyright (C) 2013-2014 Grégory Soutadé | ||||
|    | ||||
|   This file is part of gPass. | ||||
|    | ||||
| @@ -20,10 +20,10 @@ | ||||
|  | ||||
| include('functions.php'); | ||||
|  | ||||
| include('conf.php'); | ||||
|  | ||||
| session_start(); | ||||
|  | ||||
| $VIEW_CIPHERED_PASSWORDS=true; | ||||
| $ADMIN_MODE=true; | ||||
| $user = ""; | ||||
|  | ||||
| if ($ADMIN_MODE && isset($_POST['create_user'])) | ||||
| @@ -51,6 +51,11 @@ else | ||||
|   <head> | ||||
|     <meta http-equiv="Content-Type" content="text/html;charset=utf-8" > | ||||
|     <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/jssha256.js"></script> | ||||
|     <script src="ressources/hmac.js"></script> | ||||
|   | ||||
| @@ -103,7 +103,7 @@ function a2hex(str) { | ||||
| function derive_mkey(user, mkey) | ||||
| { | ||||
|     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; | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user