gPass

gPass Commit Details

Date:2017-04-17 20:39:53 (2 years 2 months ago)
Author:Grégory Soutadé
Branch:master
Commit:36db5056a3e36a1dd8e8e2588a59b0d620ddbbbf
Parents: c3fab882f24e60bc3dcfb568887eb0cfcd268146
Message:Add new encryption scheme in chrome addon/firefox webextension

Changes:
Mchrome_addon/compat.js (1 diff)
Mchrome_addon/lib/main.js (9 diffs)
Mchrome_addon/lib/misc.js (6 diffs)
Mchrome_addon/options.html (1 diff)
Mchrome_addon/options.js (2 diffs)

File differences

chrome_addon/compat.js
1919
2020
2121
22
2223
2324
2425
function getPref(key)
{
// Inspired from https://github.com/akiomik/chrome-storage-promise/
var promise = new Promise((resolve, reject) => {
chrome.storage.local.get(key, (items) => {
let err = chrome.runtime.lastError;
chrome_addon/lib/main.js
1818
1919
2020
21
21
2222
2323
2424
......
4747
4848
4949
50
50
5151
52
53
54
55
56
57
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
5868
5969
6070
......
6878
6979
7080
71
81
82
83
7284
85
86
7387
74
88
7589
76
90
7791
78
92
7993
8094
8195
82
96
8397
84
98
8599
86100
87101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
88118
89119
90120
......
96126
97127
98128
129
99130
100131
101132
......
143174
144175
145176
177
178
146179
147180
148181
149182
183
184
150185
151186
152187
188
153189
154190
155191
......
180216
181217
182218
183
184
185
186
219
220
221
222
223
224
225
226
227
228
229
230
231
232
187233
188234
189235
......
321367
322368
323369
370
371
372
324373
325374
326375
......
328377
329378
330379
331
332
333
334
335
336
380
337381
338
339
340
341
342
343
344
345
346
382
383
384
385
386
387
388
389
390
391
392
393
394
347395
348396
349397
......
373421
374422
375423
376
377
378
424
425
426
427
428
379429
380430
381
431
382432
383433
384
434
385435
386436
387
437
*/
var DEBUG = true;
var protocol_version = 3;
var protocol_version = 4;
SERVER = {OK : 0, FAILED : 1, RESTART_REQUEST : 2};
console.log(s);
}
function generate_request(domain, login, mkey)
async function generate_request(domain, login, mkey, iv, old)
{
var v = "@@" + domain + ";" + login;
debug("will encrypt " + v);
//debug("with " + a2hex(mkey));
enc = encrypt(mkey, v);
//debug("res " + a2hex(enc));
if (old)
{
var v = "@@" + domain + ";" + login;
debug("will encrypt " + v);
enc = encrypt_ecb(mkey, v);
}
else
{
var v = domain + ";" + login;
debug("will encrypt " + v);
while ((v.length % 16))
v += "\0";
hash = await digest(v);
v += hash.slice(8, 24);
enc = encrypt_cbc(mkey, iv, v);
}
return enc;
}
pbkdf2_level = await getPref("pbkdf2_level");
mkey = pbkdf2(mkey, salt, pbkdf2_level);
global_iv = await simple_pbkdf2(salt, mkey, pbkdf2_level);
global_iv = global_iv.slice(0, 16);
mkey = crypto_pbkdf2(mkey, salt, pbkdf2_level);
debug("global_iv " + a2hex(global_iv));
keys = "";
for(a=0, b=logins.length; a<logins.length; a++)
for(key_index=0, a=0; a<logins.length; a++, key_index++)
{
enc = await generate_request(domain, logins[a], mkey);
enc = await generate_request(domain, logins[a], mkey, global_iv, 0);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + a + "=" + a2hex(enc);
keys += "k" + key_index + "=" + a2hex(enc);
if (wdomain != "")
{
enc = await generate_request(wdomain, logins[a], mkey);
enc = await generate_request(wdomain, logins[a], mkey, global_iv, 0);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + (b++) + "=" + a2hex(enc);
keys += "k" + (++key_index) + "=" + a2hex(enc);
}
}
if (await getPref("crypto_v1_compatible"))
{
for(a=0; a<logins.length; a++, key_index++)
{
enc = await generate_request(domain, logins[a], mkey, global_iv, 1);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + key_index + "=" + a2hex(enc);
if (wdomain != "")
{
enc = await generate_request(wdomain, logins[a], mkey, global_iv, 1);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + (++key_index) + "=" + a2hex(enc);
}
}
}
debug("Keys " + keys);
var gPassRequest = new XMLHttpRequest();
var ciphered_password = "";
var server_pbkdf2_level = 0;
var server_version = 0;
var matched_key = 0;
var r = this.responseText.split("\n");
debug("resp " + r);
break;
case 3:
// Version 3 : nothing special to do
case 4:
// Version 3 : nothing special to do
break;
}
}
break;
case "matched_key":
matched_key = params[1];
case "pass":
ciphered_password = params[1];
break;
case "pkdbf2_level":
case "pbkdf2_level":
server_pbkdf2_level = parseInt(params[1].match(/\d+/)[0], 10);
if (server_pbkdf2_level != NaN &&
if (ciphered_password != "")
{
debug("Ciphered password : " + ciphered_password);
clear_password = await decrypt(mkey, hex2a(ciphered_password));
// Remove trailing \0 and salt
clear_password = clear_password.replace(/\0*$/, "");
clear_password = clear_password.substr(0, clear_password.length-3);
if (matched_key < logins.length)
{
clear_password = await decrypt_cbc(mkey, global_iv, hex2a(ciphered_password));
clear_password = clear_password.replace(/\0*$/, "");
clear_password = clear_password.substr(3, clear_password.length);
}
else
// Crypto v1
{
clear_password = await decrypt_ecb(mkey, hex2a(ciphered_password));
// 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;
// Remove gPass event listener and submit again with clear password
if (password.indexOf("@@") != 0 && password.indexOf("@_") != 0)
continue;
// Remove current value to limit master key stealing
field.value = "";
mkey = password.substring(2);
e.preventDefault();
var ret = ask_server(form, field, logins, domain, wdomain, mkey, (password.indexOf("@@") == 0));
ret.then(function(ret){
switch(ret)
{
case SERVER.OK:
break;
case SERVER.FAILED:
if (logins !== all_logins)
switch(ret)
{
/*ret = */ask_server(form, field, all_logins, domain, wdomain, mkey, (password.indexOf("@@") == 0));
/*if (ret == SERVER.OK)
break;};*/
}
break;
case SERVER.RESTART_REQUEST:
i = -1; // Restart loop
break;
}});
case SERVER.OK:
break;
case SERVER.FAILED:
if (logins !== all_logins)
{
ask_server(form, field, all_logins, domain, wdomain, mkey, (password.indexOf("@@") == 0));
}
break;
case SERVER.RESTART_REQUEST:
i = -1; // Restart loop
break;
}
});
}
}
async function self_test()
{
mkey = pbkdf2("password", "salt", 4096);
res = await encrypt(mkey, "DDDDDDDDDDDDDDDD");
reference = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
mkey = crypto_pbkdf2("password", "salt", 4096);
res = await encrypt_ecb(mkey, "DDDDDDDDDDDDDDDD");
reference = new Uint8Array([0xc4, 0x76, 0x01, 0x07, 0xa1, 0xc0, 0x2f, 0x22, 0xee, 0xbe, 0x60,
0xff, 0x65, 0x33, 0x5b, 0x9e]);
if (res != ab2str(reference))
{
console.log("Self test ERROR ! ");
console.log("Self test ERROR !");
}
else
console.log("All is OK ! ");
console.log("Self test OK !");
}
// self_test();
self_test();
chrome_addon/lib/misc.js
1818
1919
2020
21
21
22
2223
2324
2425
......
5253
5354
5455
55
56
57
58
56
5957
60
61
6258
6359
6460
6561
66
6762
6863
6964
......
8782
8883
8984
90
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
91118
92119
93120
94121
95122
123
96124
97125
98126
99
100
101127
102128
103129
104
130
105131
106132
107133
......
113139
114140
115141
116
142
117143
118144
119145
120146
147
121148
122
149
123150
124151
125152
......
130157
131158
132159
133
160
134161
135162
136163
......
142169
143170
144171
145
172
146173
147174
148175
176
177
149178
150179
151
180
152181
153182
154183
155184
156
185
157186
158187
159188
160189
161190
162
191
163192
164193
165194
195
196
166197
167198
168
199
169200
170201
171202
172203
173
204
174205
175206
176207
177208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
*/
var default_preferences = {"pbkdf2_level": 1000,
"account_url": "https://gpass-demo.soutade.fr/demo"};
"account_url": "https://gpass-demo.soutade.fr/demo",
"crypto_v1_compatible": true};
var browser = browser || chrome;
var crypto = crypto || window.crypto;
return bufView;
}
function returnResult(result) { return result;}
function onError(error) { console.error(error); }
function pbkdf2(mkey, salt, level)
function crypto_pbkdf2(mkey, salt, level)
{
debug("Process pbkdf2 with " + mkey);
AESCBC = {
name: "AES-CBC",
length: 256,
}
level=1000;
var key = str2ab(mkey);
return crypto.subtle.importKey("raw", key, {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"])
});
}
function _encrypt(mkey, data)
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";
debug("Encrypt " + data);
debug("Encrypt " + iv.length);
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
iv: iv
}, mkey, data)})
.then(function(encrypted) {
return ab2str(encrypted);
return promise;
}
async function _decrypt(mkey, data)
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, ab2str(pkcs7_padding));
pkcs7_padding = await _encrypt(mkey, nulliv, ab2str(pkcs7_padding));
debug("Decrypt " + data);
promise = mkey.then(function(mkey){
return crypto.subtle.decrypt({
name: "AES-CBC",
iv: nulliv
iv: iv
}, mkey, data)})
.then(function(decrypted) {
return ab2str(decrypted);
return promise;
}
async function encrypt(mkey, data)
async function encrypt_ecb(mkey, data)
{
var result = "";
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, data.slice(0, 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, data);
res = await _encrypt(mkey, nulliv, data);
result += res.slice(0, 16);
return result;
}
async function decrypt(mkey, data)
async function decrypt_ecb(mkey, data)
{
var result = "";
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, data.slice(0, 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, data);
res = await _decrypt(mkey, nulliv, data);
result += res.slice(0, 16);
return result;
}
async function encrypt_cbc(mkey, iv, 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)
{
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);
});
}
chrome_addon/options.html
1010
1111
1212
13
14
15
1316
1417
1518
<br/>
<b>PBKDF2 level</b> Number of iterations used to derivate master key <input id="pbkdf2" type="number"/><br />
<br/>
<br/>
<b>Crypto v1 compatible </b> Compatible with old crypto schema (AES ECB). Use it for encrypted passwords with server <= 0.7 <input id="crypto_v1_compatible" type="checkbox"/><br />
<br/>
<input type="button" id="save" value="Save"/>
<script type="text/javascript" src="options.js">
chrome_addon/options.js
11
2
2
3
34
45
56
67
8
79
810
911
1012
13
1114
1215
1316
......
2326
2427
2528
29
30
31
32
33
2634
2735
2836
37
2938
3039
3140
var default_preferences = {"pbkdf2_level": 1000,
"account_url": "https://gpass-demo.soutade.fr/demo"};
"account_url": "https://gpass-demo.soutade.fr/demo",
"crypto_v1_compatible": true};
function save() {
var account_url = document.getElementById('account_url').value;
var pbkdf2 = document.getElementById('pbkdf2').value;
var crypto_v1_compatible = document.getElementById('crypto_v1_compatible').checked;
chrome.storage.local.set({
'account_url': account_url,
'pbkdf2': pbkdf2,
'crypto_v1_compatible': crypto_v1_compatible,
}, function() {
alert('Saved');
});
pbkdf2 = default_preferences['pbkdf2_level'];
else
pbkdf2 = prefs['pbkdf2_level'];
if (!prefs.hasOwnProperty("crypto_v1_compatible"))
crypto_v1_compatible = default_preferences['crypto_v1_compatible'];
else
crypto_v1_compatible = prefs['crypto_v1_compatible'];
document.getElementById('account_url').value = account_url;
document.getElementById('pbkdf2').value = pbkdf2;
document.getElementById('crypto_v1_compatible').checked = crypto_v1_compatible;
});
document.getElementById('save').addEventListener("click", save);

Archive Download the corresponding diff file