280 lines
6.7 KiB
JavaScript
280 lines
6.7 KiB
JavaScript
/*
|
|
Copyright (C) 2013-2020 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;
|
|
|
|
SERVER = {OK : 0, FAILED : 1, RESTART_REQUEST : 2};
|
|
GPASS_ICON = {NORMAL:0, DISABLED:1, ACTIVATED:2};
|
|
|
|
var browser = browser || chrome;
|
|
var crypto = crypto || window.crypto;
|
|
|
|
function notify(text, data)
|
|
{
|
|
browser.runtime.sendMessage({type: "notification", options:{"message":text, "data":data}});
|
|
}
|
|
|
|
// 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){
|
|
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);
|
|
|
|
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) {
|
|
console.log("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);
|
|
});
|
|
}
|
|
|
|
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] + ".";
|
|
return res.substr(0, res.length-1);
|
|
}
|
|
// Simple xxx.com
|
|
else if (parts.length == 2)
|
|
return "*." + domain;
|
|
|
|
return "";
|
|
}
|
|
|
|
// 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);
|
|
}
|