Use native crypto functions from misc.js

This commit is contained in:
Grégory Soutadé 2017-04-17 20:37:26 +02:00
parent 241867e93c
commit 6fa296ebc4
2 changed files with 237 additions and 71 deletions

View File

@ -122,8 +122,7 @@ function a2hex(str) {
function derive_mkey(user, mkey)
{
url = url_domain(document.URL) + "/" + user;
mkey = a2hex(pkdbf2(mkey, url, pkdbf2_level, 256/8));
return mkey;
return pbkdf2(mkey, url, pbkdf2_level, 256/8);
}
var passwords;
@ -138,7 +137,7 @@ function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
this.clear_url = "";
this.clear_login = "";
this.clear_password = "";
this.masterkey = "";
this.masterkey = null;
this.salt = salt;
this.shadow_login = shadow_login;
this.access_token = "";
@ -149,16 +148,21 @@ function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
this.clear_url = "";
this.clear_login = "";
this.clear_password = "";
this.masterkey = "";
this.masterkey = null;
this.salt = salt;
}
this.encrypt = function(masterkey)
this.reset_master_key = function()
{
this.masterkey = null;
}
this.encrypt = async function(masterkey)
{
if (masterkey == this.masterkey)
return true;
if (masterkey == "" || this.clear_url == "" || this.clear_login == "")
if (masterkey == null || this.clear_url == "" || this.clear_login == "")
return false;
ciphered_login = "@@" + this.clear_url + ";" + this.clear_login;
@ -166,11 +170,8 @@ function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
// Add salt
ciphered_password = this.clear_password + generate_random(3, false);
aes = new AES();
a_masterkey = aes.init(hex2a(masterkey));
this.ciphered_login = a2hex(aes.encryptLongString(ciphered_login, a_masterkey));
this.ciphered_password = a2hex(aes.encryptLongString(ciphered_password, a_masterkey));
aes.finish();
this.ciphered_login = a2hex(await encrypt(masterkey, ciphered_login));
this.ciphered_password = a2hex(await encrypt(masterkey, ciphered_password));
this.unciphered = true;
this.masterkey = masterkey;
@ -179,21 +180,18 @@ function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
this.generate_access_token(masterkey);
}
this.decrypt = function(masterkey)
this.decrypt = async function(masterkey)
{
if (masterkey == this.masterkey && this.unciphered == true)
return true;
if (masterkey == "" || this.unciphered == true)
if (masterkey == null)
return false;
aes = new AES();
a_masterkey = aes.init(hex2a(masterkey));
login = aes.decryptLongString(hex2a(this.ciphered_login), a_masterkey);
if (masterkey == this.masterkey)
return (this.unciphered == true);
login = await decrypt(masterkey, hex2a(this.ciphered_login));
login = login.replace(/\0*$/, "");
if (login.indexOf("@@") != 0)
{
aes.finish();
return false;
}
// Remove @@
@ -201,10 +199,9 @@ function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
infos = login.split(";");
this.clear_url = infos[0];
this.clear_login = infos[1];
this.clear_password = aes.decryptLongString(hex2a(this.ciphered_password), a_masterkey);
this.clear_password = await decrypt(masterkey, hex2a(this.ciphered_password));
this.unciphered = true;
this.masterkey = masterkey;
aes.finish();
// Remove salt
this.clear_password = this.clear_password.replace(/\0*$/, "");
@ -215,7 +212,7 @@ function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
this.isUnciphered = function(masterkey)
{
return (this.unciphered == true && masterkey == this.masterkey && masterkey != "")
return (this.unciphered == true && masterkey == this.masterkey && masterkey != null)
}
this.isCiphered = function(masterkey)
@ -223,28 +220,24 @@ function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
return !(this.isUnciphered(masterkey));
}
this.shadow_login_to_access_token = function(masterkey)
this.shadow_login_to_access_token = async function(masterkey)
{
var aes = new AES();
var key = pkdbf2(hex2a(masterkey), hex2a(this.salt), pkdbf2_level, 256/8);
var a_key = aes.init(hex2a(key));
this.access_token = aes.encryptLongString(hex2a(this.shadow_login), a_key);
this.access_token = await encrypt(masterkey, hex2a(this.shadow_login));
this.access_token = a2hex(this.access_token);
aes.finish();
}
this.generate_access_token = function(masterkey)
this.generate_access_token = async function(masterkey)
{
this.salt = a2hex(generate_random(16, false));
this.shadow_login = a2hex(generate_random(16, false));
return this.shadow_login_to_access_token(masterkey);
return await this.shadow_login_to_access_token(masterkey);
}
}
function clearMasterKey()
{
current_mkey = "";
current_mkey = null;
for(i=0; i<passwords.length; i++)
{
@ -328,17 +321,17 @@ function update_stats()
}
// Remove all password without credentials
function put_ciphered_credentials(passwords, masterkey)
async function put_ciphered_credentials(passwords, masterkey)
{
for(var i=0; i<passwords.length; i++)
{
passwords[i].generate_access_token(masterkey);
await passwords[i].generate_access_token(masterkey);
remove_password_server(current_user, passwords[i].ciphered_login, '');
add_password_server(current_user, passwords[i]);
}
}
function get_ciphered_credentials(masterkey)
async function get_ciphered_credentials(masterkey)
{
access_tokens = '';
old_passwords = new Array();
@ -353,13 +346,13 @@ function get_ciphered_credentials(masterkey)
continue;
}
passwords[i].shadow_login_to_access_token(masterkey);
await passwords[i].shadow_login_to_access_token(masterkey);
if (access_tokens.length) access_tokens += ",";
access_tokens += passwords[i].access_token;
}
if (old_passwords.length)
put_ciphered_credentials(old_passwords, masterkey);
await put_ciphered_credentials(old_passwords, masterkey);
if (!access_tokens.length)
return;
@ -385,16 +378,16 @@ function get_ciphered_credentials(masterkey)
req.send("get_secure_passwords=1&user=" + user + "&access_tokens=" + access_tokens);
}
function change_master_key(warning_unciphered)
async function change_master_key(warning_unciphered)
{
var nb_unciphered = 0;
if (current_mkey.length && use_shadow_logins)
get_ciphered_credentials(current_mkey);
if (current_mkey && use_shadow_logins)
await get_ciphered_credentials(current_mkey);
for(i=0; i<passwords.length; i++)
{
if (passwords[i].decrypt(current_mkey))
if (await passwords[i].decrypt(current_mkey))
nb_unciphered++;
}
@ -543,11 +536,16 @@ function update_master_key(warning_unciphered)
if (current_mkey != "")
{
for(i=0; i<passwords.length; i++)
{
passwords[i].reset_master_key();
}
current_mkey = derive_mkey(current_user, current_mkey);
startClearTimer();
}
else
{
current_mkey = null;
// Disable warning on empty master key (clear passwords from others)
warning_unciphered = false;
stopClearTimer();
@ -584,7 +582,7 @@ function add_password_server(user, pentry)
return ok;
}
function construct_pentry(user, url, password, login, mkey, derive_masterkey)
async function construct_pentry(user, url, password, login, mkey, derive_masterkey)
{
var ret = null;
@ -632,7 +630,7 @@ function construct_pentry(user, url, password, login, mkey, derive_masterkey)
pentry.clear_url = url;
pentry.clear_login = login;
pentry.clear_password = password;
pentry.encrypt(mkey);
await pentry.encrypt(mkey);
return pentry;
}
@ -679,28 +677,33 @@ function add_password()
mkey = inputs[i].value;
}
pentry = construct_pentry(current_user, url, password, login, mkey, true)
construct_pentry(current_user, url, password, login, mkey, true).then(
function (pentry) {
if (pentry == null) return false;
if (pentry == null) return;
res = add_password_server(current_user, pentry);
res = add_password_server(current_user, pentry);
if (!res) return false;
if (!res) return false;
for(i=0; i<passwords.length; i++)
{
passwords[i].reset_master_key();
}
current_mkey = pentry.masterkey;
passwords.push(pentry);
passwords.push(pentry);
change_master_key(false);
current_mkey = pentry.masterkey;
change_master_key(false);
for(i=0; i<inputs.length; i++)
{
if (inputs[i].getAttribute("type") == "text" ||
inputs[i].getAttribute("type") == "password")
inputs[i].value = "";
}
for(i=0; i<inputs.length; i++)
{
if (inputs[i].getAttribute("type") == "text" ||
inputs[i].getAttribute("type") == "password")
inputs[i].value = "";
}
startClearTimer();
startClearTimer();
});
return true;
}
@ -816,23 +819,24 @@ function update_entry(entry_number)
if(!confirm("Are you sure want to update this entry ?"))
return;
pentry = construct_pentry(current_user, url, password, login, current_mkey, false);
construct_pentry(current_user, url, password, login, current_mkey, false).then(
function (pentry) {
if (pentry == null) return;
if (pentry == null) return;
ok = remove_password_server(current_user, passwords[found].ciphered_login, passwords[found].access_token);
if (!ok) return;
ok = remove_password_server(current_user, passwords[found].ciphered_login, passwords[found].access_token);
if (!ok) return;
ok = add_password_server(current_user, pentry);
if (!ok) return;
ok = add_password_server(current_user, pentry);
if (!ok) return;
passwords[found] = pentry;
ciphered_login.setAttribute("login", pentry.ciphered_login);
passwords[found] = pentry;
ciphered_login.setAttribute("login", pentry.ciphered_login);
alert("Entry updated");
alert("Entry updated");
});
}
function update_masterkey()
async function update_masterkey()
{
var url = "";
var login = "";
@ -858,7 +862,7 @@ function update_masterkey()
var found = 0;
for(i=0; i<passwords.length; i++)
{
if (passwords[i].decrypt(oldmkey))
if (await passwords[i].decrypt(oldmkey))
{
ok = remove_password_server(current_user, passwords[i].ciphered_login, passwords[i].access_token);
if (!ok)
@ -867,7 +871,7 @@ function update_masterkey()
break;
}
passwords[i].encrypt(current_mkey);
await passwords[i].encrypt(current_mkey);
ok = add_password_server(current_user, passwords[i]);
if (!ok)

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

@ -0,0 +1,162 @@
/*
Copyright (C) 2013-2017 Grégory Soutadé
This file is part of gPass.
gPass is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gPass is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
var default_preferences = {"pbkdf2_level": 1000,
"account_url": "https://gpass-demo.soutade.fr/demo"};
var browser = browser || chrome;
var crypto = crypto || window.crypto;
// https://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
function str2ab2(str) {
var chars = []
for (var i=0, strLen=str.length; i < strLen; i++) {
chars.push(str.charCodeAt(i));
}
return new Uint8Array(chars);
}
function str2ab(str) {
var buf = new ArrayBuffer(str.length); // 2 bytes for each char
// var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
var bufView = new Uint8Array(buf);
for (var i=0, strLen=str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return bufView;
}
function 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 _encrypt(mkey, data)
{
while ((data.length % 16))
data += "\0";
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 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));
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: 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;
}