diff --git a/firefox_webextension/background.js b/firefox_webextension/background.js new file mode 100644 index 0000000..cee92d1 --- /dev/null +++ b/firefox_webextension/background.js @@ -0,0 +1,17 @@ +browser.runtime.onMessage.addListener( + function(request) { + + if (request.type == "notification") + { + options = { + type: "basic", + title : "gPass", + message : request.options.message, + iconUrl:browser.extension.getURL("gpass_icon_64.png") + }; + + browser.notifications.create("gPass", options); + + window.setTimeout(function() {browser.notifications.clear("gPass")}, 2000); + } + }); diff --git a/firefox_webextension/compat.js b/firefox_webextension/compat.js new file mode 100644 index 0000000..2ce1e50 --- /dev/null +++ b/firefox_webextension/compat.js @@ -0,0 +1,220 @@ +/* + Copyright (C) 2013-2016 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 . +*/ + +var default_preferences = {"pbkdf2_level": 1000, + "account_url": "https://gpass-demo.soutade.fr/demo"}; + +function notify(text, data) +{ + browser.runtime.sendMessage({type: "notification", options:{"message":text}}); +} +// 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 returnResult(result) { return result;} +function onError(error) { console.error(error); } + +function pbkdf2(mkey, salt, level) +{ + debug("Process pbkdf2 with " + mkey); + + AESCBC = { + name: "AES-CBC", + length: 256, + } + level=1000; + + var key = str2ab(mkey); + return window.crypto.subtle.importKey("raw", key, {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"]) + .then(function(key){ + //sha-256 + return window.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 _encrypt(mkey, data) +{ + while ((data.length % 16)) + data += "\0"; + + debug("Encrypt " + data); + + data = str2ab(data); + + 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 window.crypto.subtle.encrypt({ + name: "AES-CBC", + iv: nulliv + }, mkey, data)}) + .then(function(encrypted) { + return ab2str(encrypted); + }) + .catch(function(encryption) { + console.log("Encryption rejected " + encryption); + }); + + return promise; +} + +async function _decrypt(mkey, data) +{ + while ((data.length % 16)) + data += "\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, ab2str(pkcs7_padding)); + + debug("Decrypt " + data); + + 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 window.crypto.subtle.decrypt({ + name: "AES-CBC", + iv: nulliv + }, mkey, data)}) + .then(function(decrypted) { + return ab2str(decrypted); + }) + .catch(function(decryption) { + console.log("Decryption rejected " + decryption); + }); + + return promise; +} + +async function encrypt(mkey, data) +{ + var result = ""; + + while (data.length > 16) + { + res = await _encrypt(mkey, data.slice(0, 16)); + // Remove PKCS7 padding + result += res.slice(0, 16); + data = data.slice(16); + } + res = await _encrypt(mkey, data); + result += res.slice(0, 16); + + return result; +} + +async function decrypt(mkey, data) +{ + var result = ""; + + while (data.length > 16) + { + res = await _decrypt(mkey, data.slice(0, 16)); + // Remove PKCS7 padding + result += res.slice(0, 16); + data = data.slice(16); + } + res = await _decrypt(mkey, data); + result += res.slice(0, 16); + + return result; +} + +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}); +} + +// function getPref(key) +// { +// res = localStorage.getItem(key); +// if (res == null) +// return default_preferences[key]; +// return res; +// } + +// function setPref(key, value) +// { +// localStorage.setItem(key, value); +// } + +// function load_preferences() +// { +// preferences = {}; + +// for (var i = 0; i < localStorage.length; i++) +// { +// k = localStorage.key(i); +// preferences[k] = getPref[k]; +// } + +// return preferences; +// } diff --git a/firefox_webextension/gpass_icon_128.png b/firefox_webextension/gpass_icon_128.png new file mode 120000 index 0000000..bca1e80 --- /dev/null +++ b/firefox_webextension/gpass_icon_128.png @@ -0,0 +1 @@ +../resources/gpass_icon_128.png \ No newline at end of file diff --git a/firefox_webextension/gpass_icon_16.png b/firefox_webextension/gpass_icon_16.png new file mode 120000 index 0000000..b05d308 --- /dev/null +++ b/firefox_webextension/gpass_icon_16.png @@ -0,0 +1 @@ +../resources/gpass_icon_16.png \ No newline at end of file diff --git a/firefox_webextension/gpass_icon_32.png b/firefox_webextension/gpass_icon_32.png new file mode 120000 index 0000000..c8f1295 --- /dev/null +++ b/firefox_webextension/gpass_icon_32.png @@ -0,0 +1 @@ +../resources/gpass_icon_32.png \ No newline at end of file diff --git a/firefox_webextension/gpass_icon_64.png b/firefox_webextension/gpass_icon_64.png new file mode 120000 index 0000000..06472a1 --- /dev/null +++ b/firefox_webextension/gpass_icon_64.png @@ -0,0 +1 @@ +../resources/gpass_icon_64.png \ No newline at end of file diff --git a/firefox_webextension/lib b/firefox_webextension/lib new file mode 120000 index 0000000..850519c --- /dev/null +++ b/firefox_webextension/lib @@ -0,0 +1 @@ +../chrome_addon/lib/ \ No newline at end of file diff --git a/firefox_webextension/manifest.json b/firefox_webextension/manifest.json new file mode 100644 index 0000000..d1913f1 --- /dev/null +++ b/firefox_webextension/manifest.json @@ -0,0 +1,35 @@ +{ + "manifest_version": 2, + + "name": "gPass", + "short_name": "gPass", + "version": "0.8", + "description": "gPass : global password manager", + "icons" : {"16":"gpass_icon_16.png", "32":"gpass_icon_32.png", "64":"gpass_icon_64.png", "128":"gpass_icon_128.png"}, + "author" : "Grégory Soutadé", + "homepage_url" : "http://indefero.soutade.fr/p/gpass", + + "content_scripts": [ + { + "matches": ["https://*/*", "http://*/*"], + "js": ["lib/parseuri.js", "compat.js", "lib/main.js"], + "run_at" : "document_idle", + "all_frames" : true + } + ], + + "background": { + "persistent": false, + "scripts": ["background.js"] + }, + + "options_ui": { "page":"options.html" }, + + "permissions": [ + "https://*/", + "http://*/", + "notifications", + "storage", + "activeTab" + ] +} diff --git a/firefox_webextension/options.html b/firefox_webextension/options.html new file mode 120000 index 0000000..1ea0970 --- /dev/null +++ b/firefox_webextension/options.html @@ -0,0 +1 @@ +../chrome_addon/options.html \ No newline at end of file diff --git a/firefox_webextension/options.js b/firefox_webextension/options.js new file mode 100644 index 0000000..f3f24f2 --- /dev/null +++ b/firefox_webextension/options.js @@ -0,0 +1,35 @@ +var default_preferences = {"pbkdf2_level": 1000, + "account_url": "https://gpass-demo.soutade.fr/demo"}; + +function save() { + var account_url = document.getElementById('account_url').value; + var pbkdf2 = document.getElementById('pbkdf2').value; + + browser.storage.local.set({ + "account_url":account_url, + "pbkdf2_level":pbkdf2 + }) + .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']; + + 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"]; + } + ); +} + +document.getElementById('save').addEventListener("click", save); +document.addEventListener("DOMContentLoaded", restoreOptions);