From 6fa296ebc440e1013dcaae5e2cbc6c562cfdb960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Mon, 17 Apr 2017 20:37:26 +0200 Subject: [PATCH] Use native crypto functions from misc.js --- server/resources/gpass.js | 146 +++++++++++++++++----------------- server/resources/misc.js | 162 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+), 71 deletions(-) create mode 100644 server/resources/misc.js diff --git a/server/resources/gpass.js b/server/resources/gpass.js index 1b2e29a..271958b 100755 --- a/server/resources/gpass.js +++ b/server/resources/gpass.js @@ -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. +*/ + +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; +}