megar 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/.travis.yml +11 -0
- data/CHANGELOG +5 -0
- data/Gemfile +4 -0
- data/Guardfile +11 -0
- data/LICENSE +22 -0
- data/README.rdoc +218 -0
- data/Rakefile +33 -0
- data/bin/megar +16 -0
- data/lib/extensions/math.rb +13 -0
- data/lib/js_ref_impl/_README +9 -0
- data/lib/js_ref_impl/base64_1.js +83 -0
- data/lib/js_ref_impl/crypto_5.js +1795 -0
- data/lib/js_ref_impl/download_8.js +867 -0
- data/lib/js_ref_impl/hex_1.js +76 -0
- data/lib/js_ref_impl/index_9.js +666 -0
- data/lib/js_ref_impl/js.manifest +115 -0
- data/lib/js_ref_impl/rsa_1.js +456 -0
- data/lib/js_ref_impl/sjcl_1.js +1 -0
- data/lib/js_ref_impl/upload_10.js +691 -0
- data/lib/megar.rb +11 -0
- data/lib/megar/catalog.rb +5 -0
- data/lib/megar/catalog/catalog_item.rb +90 -0
- data/lib/megar/catalog/file.rb +14 -0
- data/lib/megar/catalog/files.rb +13 -0
- data/lib/megar/catalog/folder.rb +20 -0
- data/lib/megar/catalog/folders.rb +28 -0
- data/lib/megar/connection.rb +84 -0
- data/lib/megar/crypto.rb +399 -0
- data/lib/megar/exception.rb +55 -0
- data/lib/megar/session.rb +157 -0
- data/lib/megar/shell.rb +87 -0
- data/lib/megar/version.rb +3 -0
- data/megar.gemspec +30 -0
- data/spec/fixtures/crypto_expectations/sample_user.json +109 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/crypto_expectations_helper.rb +44 -0
- data/spec/support/mocks_helper.rb +22 -0
- data/spec/unit/catalog/file_spec.rb +31 -0
- data/spec/unit/catalog/files_spec.rb +26 -0
- data/spec/unit/catalog/folder_spec.rb +28 -0
- data/spec/unit/catalog/folders_spec.rb +49 -0
- data/spec/unit/connection_spec.rb +50 -0
- data/spec/unit/crypto_spec.rb +476 -0
- data/spec/unit/exception_spec.rb +35 -0
- data/spec/unit/extensions/math_spec.rb +21 -0
- data/spec/unit/session_spec.rb +146 -0
- data/spec/unit/shell_spec.rb +18 -0
- metadata +238 -0
@@ -0,0 +1,1795 @@
|
|
1
|
+
window.URL = window.URL || window.webkitURL;
|
2
|
+
var have_ab = typeof ArrayBuffer != 'undefined' && typeof DataView != 'undefined';
|
3
|
+
var use_workers = have_ab && typeof Worker != 'undefined';
|
4
|
+
|
5
|
+
if ((navigator.appVersion.indexOf('Safari') > 0) && (navigator.appVersion.indexOf('Version/5') > 0))
|
6
|
+
{
|
7
|
+
use_workers=false;
|
8
|
+
have_ab=false;
|
9
|
+
}
|
10
|
+
|
11
|
+
var ssl_off = [ 'Firefox/14', 'Firefox/15', 'Firefox/17', 'Safari', 'Firefox/16' ];
|
12
|
+
var ssl_opt = [ 'Chrome/' ];
|
13
|
+
|
14
|
+
function ssl_needed()
|
15
|
+
{
|
16
|
+
for (var i = ssl_opt.length; i--; ) if (navigator.userAgent.indexOf(ssl_opt[i]) >= 0) return 0;
|
17
|
+
for (var i = ssl_off.length; i--; ) if (navigator.userAgent.indexOf(ssl_off[i]) >= 0) return -1;
|
18
|
+
return 1;
|
19
|
+
}
|
20
|
+
|
21
|
+
var use_ssl = ssl_needed();
|
22
|
+
if (!use_ssl && localStorage.use_ssl) use_ssl = 1;
|
23
|
+
else use_ssl++;
|
24
|
+
|
25
|
+
var chromehack = navigator.appVersion.indexOf('Chrome/');
|
26
|
+
chromehack = chromehack >= 0 && parseInt(navigator.appVersion.substr(chromehack+7)) > 21;
|
27
|
+
|
28
|
+
// keyboard/mouse entropy
|
29
|
+
eventsCollect();
|
30
|
+
|
31
|
+
var EINTERNAL = -1;
|
32
|
+
var EARGS = -2;
|
33
|
+
var EAGAIN = -3;
|
34
|
+
var ERATELIMIT = -4;
|
35
|
+
var EFAILED = -5;
|
36
|
+
var ETOOMANY = -6; // too many IP addresses
|
37
|
+
var ERANGE = -7; // file packet out of range
|
38
|
+
var EEXPIRED = -8;
|
39
|
+
|
40
|
+
// FS access errors
|
41
|
+
var ENOENT = -9;
|
42
|
+
var ECIRCULAR = -10;
|
43
|
+
var EACCESS = -11;
|
44
|
+
var EEXIST = -12;
|
45
|
+
var EINCOMPLETE = -13;
|
46
|
+
|
47
|
+
// crypto errors
|
48
|
+
var EKEY = -14;
|
49
|
+
|
50
|
+
// user errors
|
51
|
+
var ESID = -15;
|
52
|
+
var EBLOCKED = -16;
|
53
|
+
var EOVERQUOTA = -17;
|
54
|
+
var ETEMPUNAVAIL = -18;
|
55
|
+
var ETOOMANYCONNECTIONS = -19;
|
56
|
+
|
57
|
+
function benchmark()
|
58
|
+
{
|
59
|
+
var a = Array(1048577).join('a');
|
60
|
+
|
61
|
+
var ab = str_to_ab(a);
|
62
|
+
|
63
|
+
var ab8 = new Uint8Array(ab);
|
64
|
+
|
65
|
+
var aes = new sjcl.cipher.aes([0,1,2,3]);
|
66
|
+
|
67
|
+
t = new Date().getTime();
|
68
|
+
for (var i = 16; i--; ) encrypt_ab_ctr(aes,ab8,[1,2],30000);
|
69
|
+
t = new Date().getTime()-t;
|
70
|
+
|
71
|
+
console.log((a.length*16/1024)/(t/1000) + " KB/s");
|
72
|
+
}
|
73
|
+
|
74
|
+
var seqno = rand(0x100000000);
|
75
|
+
|
76
|
+
// compute final MAC from block MACs
|
77
|
+
function condenseMacs(macs,key)
|
78
|
+
{
|
79
|
+
var i, aes;
|
80
|
+
mac = [0,0,0,0];
|
81
|
+
|
82
|
+
aes = new sjcl.cipher.aes([key[0],key[1],key[2],key[3]]);
|
83
|
+
|
84
|
+
for (i = 0; i < macs.length; i++)
|
85
|
+
{
|
86
|
+
mac[0] ^= macs[i][0];
|
87
|
+
mac[1] ^= macs[i][1];
|
88
|
+
mac[2] ^= macs[i][2];
|
89
|
+
mac[3] ^= macs[i][3];
|
90
|
+
|
91
|
+
mac = aes.encrypt(mac);
|
92
|
+
}
|
93
|
+
|
94
|
+
return mac;
|
95
|
+
}
|
96
|
+
|
97
|
+
// convert user-supplied password array
|
98
|
+
function prepare_key(a)
|
99
|
+
{
|
100
|
+
var i, j, r;
|
101
|
+
var pkey = [0x93C467E3,0x7DB0C7A4,0xD1BE3F81,0x0152CB56];
|
102
|
+
|
103
|
+
for (r = 65536; r--; )
|
104
|
+
{
|
105
|
+
for (j = 0; j < a.length; j += 4)
|
106
|
+
{
|
107
|
+
key = [0,0,0,0];
|
108
|
+
|
109
|
+
for (i = 0; i < 4; i++) if (i+j < a.length) key[i] = a[i+j];
|
110
|
+
|
111
|
+
aes = new sjcl.cipher.aes(key);
|
112
|
+
pkey = aes.encrypt(pkey);
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
return pkey;
|
117
|
+
}
|
118
|
+
|
119
|
+
// prepare_key with string input
|
120
|
+
function prepare_key_pw(password)
|
121
|
+
{
|
122
|
+
return prepare_key(str_to_a32(password));
|
123
|
+
}
|
124
|
+
|
125
|
+
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=";
|
126
|
+
var b64a = b64.split('');
|
127
|
+
|
128
|
+
|
129
|
+
// unsubstitute standard base64 special characters, restore padding
|
130
|
+
function base64urldecode(data)
|
131
|
+
{
|
132
|
+
data += '=='.substr((2-data.length*3)&3)
|
133
|
+
|
134
|
+
if (typeof atob === 'function')
|
135
|
+
{
|
136
|
+
data = data.replace(/\-/g,'+').replace(/_/g,'/').replace(/,/g,'');
|
137
|
+
|
138
|
+
try {
|
139
|
+
return atob(data);
|
140
|
+
} catch (e) {
|
141
|
+
return '';
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
// http://kevin.vanzonneveld.net
|
146
|
+
// + original by: Tyler Akins (http://rumkin.com)
|
147
|
+
// + improved by: Thunder.m
|
148
|
+
// + input by: Aman Gupta
|
149
|
+
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
150
|
+
// + bugfixed by: Onno Marsman
|
151
|
+
// + bugfixed by: Pellentesque Malesuada
|
152
|
+
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
153
|
+
// + input by: Brett Zamir (http://brett-zamir.me)
|
154
|
+
// + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
155
|
+
// * example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA==');
|
156
|
+
// * returns 1: 'Kevin van Zonneveld'
|
157
|
+
// mozilla has this native
|
158
|
+
// - but breaks in 2.0.0.12!
|
159
|
+
//if (typeof this.window['atob'] == 'function') {
|
160
|
+
// return atob(data);
|
161
|
+
//}
|
162
|
+
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
|
163
|
+
ac = 0,
|
164
|
+
dec = "",
|
165
|
+
tmp_arr = [];
|
166
|
+
|
167
|
+
if (!data) {
|
168
|
+
return data;
|
169
|
+
}
|
170
|
+
|
171
|
+
data += '';
|
172
|
+
|
173
|
+
do { // unpack four hexets into three octets using index points in b64
|
174
|
+
h1 = b64.indexOf(data.charAt(i++));
|
175
|
+
h2 = b64.indexOf(data.charAt(i++));
|
176
|
+
h3 = b64.indexOf(data.charAt(i++));
|
177
|
+
h4 = b64.indexOf(data.charAt(i++));
|
178
|
+
|
179
|
+
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
|
180
|
+
|
181
|
+
o1 = bits >> 16 & 0xff;
|
182
|
+
o2 = bits >> 8 & 0xff;
|
183
|
+
o3 = bits & 0xff;
|
184
|
+
|
185
|
+
if (h3 == 64) {
|
186
|
+
tmp_arr[ac++] = String.fromCharCode(o1);
|
187
|
+
} else if (h4 == 64) {
|
188
|
+
tmp_arr[ac++] = String.fromCharCode(o1, o2);
|
189
|
+
} else {
|
190
|
+
tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
|
191
|
+
}
|
192
|
+
} while (i < data.length);
|
193
|
+
|
194
|
+
dec = tmp_arr.join('');
|
195
|
+
|
196
|
+
return dec;
|
197
|
+
}
|
198
|
+
|
199
|
+
// substitute standard base64 special characters to prevent JSON escaping, remove padding
|
200
|
+
function base64urlencode(data)
|
201
|
+
{
|
202
|
+
if (typeof btoa === 'function') return btoa(data).replace(/\+/g,'-').replace(/\//g,'_').replace(/=/g,'');
|
203
|
+
|
204
|
+
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
|
205
|
+
ac = 0,
|
206
|
+
enc = "",
|
207
|
+
tmp_arr = [];
|
208
|
+
|
209
|
+
do { // pack three octets into four hexets
|
210
|
+
o1 = data.charCodeAt(i++);
|
211
|
+
o2 = data.charCodeAt(i++);
|
212
|
+
o3 = data.charCodeAt(i++);
|
213
|
+
|
214
|
+
bits = o1 << 16 | o2 << 8 | o3;
|
215
|
+
|
216
|
+
h1 = bits >> 18 & 0x3f;
|
217
|
+
h2 = bits >> 12 & 0x3f;
|
218
|
+
h3 = bits >> 6 & 0x3f;
|
219
|
+
h4 = bits & 0x3f;
|
220
|
+
|
221
|
+
// use hexets to index into b64, and append result to encoded string
|
222
|
+
tmp_arr[ac++] = b64a[h1] + b64a[h2] + b64a[h3] + b64a[h4];
|
223
|
+
} while (i < data.length);
|
224
|
+
|
225
|
+
enc = tmp_arr.join('');
|
226
|
+
var r = data.length % 3;
|
227
|
+
return (r ? enc.slice(0, r - 3) : enc);
|
228
|
+
}
|
229
|
+
|
230
|
+
// array of 32-bit words to string (big endian)
|
231
|
+
function a32_to_str(a)
|
232
|
+
{
|
233
|
+
var b = '';
|
234
|
+
|
235
|
+
for (var i = 0; i < a.length*4; i++)
|
236
|
+
b = b+String.fromCharCode((a[i>>2] >>> (24-(i & 3)*8)) & 255);
|
237
|
+
|
238
|
+
return b;
|
239
|
+
}
|
240
|
+
|
241
|
+
function a32_to_base64(a)
|
242
|
+
{
|
243
|
+
return base64urlencode(a32_to_str(a));
|
244
|
+
}
|
245
|
+
|
246
|
+
// string to array of 32-bit words (big endian)
|
247
|
+
function str_to_a32(b)
|
248
|
+
{
|
249
|
+
var a = Array((b.length+3) >> 2);
|
250
|
+
|
251
|
+
for (var i = 0; i < b.length; i++) a[i>>2] |= (b.charCodeAt(i) << (24-(i & 3)*8));
|
252
|
+
|
253
|
+
return a;
|
254
|
+
}
|
255
|
+
|
256
|
+
function base64_to_a32(s)
|
257
|
+
{
|
258
|
+
return str_to_a32(base64urldecode(s));
|
259
|
+
}
|
260
|
+
|
261
|
+
// ArrayBuffer to binary string
|
262
|
+
function ab_to_str(ab)
|
263
|
+
{
|
264
|
+
var b = '', i;
|
265
|
+
|
266
|
+
if (have_ab)
|
267
|
+
{
|
268
|
+
var b = '';
|
269
|
+
|
270
|
+
var ab8 = new Uint8Array(ab);
|
271
|
+
|
272
|
+
for (i = 0; i < ab8.length; i++) b = b+String.fromCharCode(ab8[i]);
|
273
|
+
}
|
274
|
+
else
|
275
|
+
{
|
276
|
+
return ab.buffer;
|
277
|
+
}
|
278
|
+
|
279
|
+
return b;
|
280
|
+
}
|
281
|
+
|
282
|
+
// ArrayBuffer to binary string
|
283
|
+
function ab_to_base64(ab)
|
284
|
+
{
|
285
|
+
return base64urlencode(ab_to_str(ab));
|
286
|
+
}
|
287
|
+
|
288
|
+
// ArrayBuffer to binary with depadding
|
289
|
+
function ab_to_str_depad(ab)
|
290
|
+
{
|
291
|
+
var b, i;
|
292
|
+
|
293
|
+
if (have_ab)
|
294
|
+
{
|
295
|
+
b = '';
|
296
|
+
|
297
|
+
var ab8 = new Uint8Array(ab);
|
298
|
+
|
299
|
+
for (i = 0; i < ab8.length && ab8[i]; i++) b = b+String.fromCharCode(ab8[i]);
|
300
|
+
}
|
301
|
+
else
|
302
|
+
{
|
303
|
+
b = ab_to_str(ab);
|
304
|
+
|
305
|
+
for (i = b.length; i-- && !b.charCodeAt(i); );
|
306
|
+
|
307
|
+
b = b.substr(0,i+1);
|
308
|
+
}
|
309
|
+
|
310
|
+
return b;
|
311
|
+
}
|
312
|
+
|
313
|
+
// binary string to ArrayBuffer, 0-padded to AES block size
|
314
|
+
function str_to_ab(b)
|
315
|
+
{
|
316
|
+
var ab, i;
|
317
|
+
|
318
|
+
if (have_ab)
|
319
|
+
{
|
320
|
+
ab = new ArrayBuffer((b.length+15)&-16);
|
321
|
+
var ab8 = new Uint8Array(ab);
|
322
|
+
|
323
|
+
for (i = b.length; i--; ) ab8[i] = b.charCodeAt(i);
|
324
|
+
|
325
|
+
return ab;
|
326
|
+
}
|
327
|
+
else
|
328
|
+
{
|
329
|
+
b += Array(16-((b.length-1)&15)).join(String.fromCharCode(0));
|
330
|
+
|
331
|
+
ab = { buffer : b };
|
332
|
+
}
|
333
|
+
|
334
|
+
return ab;
|
335
|
+
}
|
336
|
+
|
337
|
+
// binary string to ArrayBuffer, 0-padded to AES block size
|
338
|
+
function base64_to_ab(a)
|
339
|
+
{
|
340
|
+
return str_to_ab(base64urldecode(a));
|
341
|
+
}
|
342
|
+
|
343
|
+
// encrypt ArrayBuffer in CTR mode, return MAC
|
344
|
+
function encrypt_ab_ctr(aes,ab,nonce,pos)
|
345
|
+
{
|
346
|
+
var ctr = [nonce[0],nonce[1],(pos/0x1000000000) >>> 0,(pos/0x10) >>> 0];
|
347
|
+
var mac = [ctr[0],ctr[1],ctr[0],ctr[1]];
|
348
|
+
|
349
|
+
var enc, i, j, len, v;
|
350
|
+
|
351
|
+
if (have_ab)
|
352
|
+
{
|
353
|
+
var data0, data1, data2, data3;
|
354
|
+
|
355
|
+
len = ab.buffer.byteLength-16;
|
356
|
+
|
357
|
+
var v = new DataView(ab.buffer);
|
358
|
+
|
359
|
+
for (i = 0; i < len; i += 16)
|
360
|
+
{
|
361
|
+
data0 = v.getUint32(i,false);
|
362
|
+
data1 = v.getUint32(i+4,false);
|
363
|
+
data2 = v.getUint32(i+8,false);
|
364
|
+
data3 = v.getUint32(i+12,false);
|
365
|
+
|
366
|
+
// compute MAC
|
367
|
+
mac[0] ^= data0;
|
368
|
+
mac[1] ^= data1;
|
369
|
+
mac[2] ^= data2;
|
370
|
+
mac[3] ^= data3;
|
371
|
+
mac = aes.encrypt(mac);
|
372
|
+
|
373
|
+
// encrypt using CTR
|
374
|
+
enc = aes.encrypt(ctr);
|
375
|
+
v.setUint32(i,data0 ^ enc[0],false);
|
376
|
+
v.setUint32(i+4,data1 ^ enc[1],false);
|
377
|
+
v.setUint32(i+8,data2 ^ enc[2],false);
|
378
|
+
v.setUint32(i+12,data3 ^ enc[3],false);
|
379
|
+
|
380
|
+
if (!(++ctr[3])) ctr[2]++;
|
381
|
+
}
|
382
|
+
|
383
|
+
if (i < ab.buffer.byteLength)
|
384
|
+
{
|
385
|
+
var fullbuf = new Uint8Array(ab.buffer);
|
386
|
+
var tmpbuf = new ArrayBuffer(16);
|
387
|
+
var tmparray = new Uint8Array(tmpbuf);
|
388
|
+
|
389
|
+
tmparray.set(fullbuf.subarray(i));
|
390
|
+
|
391
|
+
v = new DataView(tmpbuf);
|
392
|
+
|
393
|
+
enc = aes.encrypt(ctr);
|
394
|
+
|
395
|
+
data0 = v.getUint32(0,false);
|
396
|
+
data1 = v.getUint32(4,false);
|
397
|
+
data2 = v.getUint32(8,false);
|
398
|
+
data3 = v.getUint32(12,false);
|
399
|
+
|
400
|
+
mac[0] ^= data0;
|
401
|
+
mac[1] ^= data1;
|
402
|
+
mac[2] ^= data2;
|
403
|
+
mac[3] ^= data3;
|
404
|
+
mac = aes.encrypt(mac);
|
405
|
+
|
406
|
+
enc = aes.encrypt(ctr);
|
407
|
+
v.setUint32(0,data0 ^ enc[0],false);
|
408
|
+
v.setUint32(4,data1 ^ enc[1],false);
|
409
|
+
v.setUint32(8,data2 ^ enc[2],false);
|
410
|
+
v.setUint32(12,data3 ^ enc[3],false);
|
411
|
+
|
412
|
+
fullbuf.set(tmparray.subarray(0,j = fullbuf.length-i),i);
|
413
|
+
}
|
414
|
+
}
|
415
|
+
else
|
416
|
+
{
|
417
|
+
var ab32 = _str_to_a32(ab.buffer);
|
418
|
+
|
419
|
+
len = ab32.length-3;
|
420
|
+
|
421
|
+
for (i = 0; i < len; i += 4)
|
422
|
+
{
|
423
|
+
mac[0] ^= ab32[i];
|
424
|
+
mac[1] ^= ab32[i+1];
|
425
|
+
mac[2] ^= ab32[i+2];
|
426
|
+
mac[3] ^= ab32[i+3];
|
427
|
+
mac = aes.encrypt(mac);
|
428
|
+
|
429
|
+
enc = aes.encrypt(ctr);
|
430
|
+
ab32[i] ^= enc[0];
|
431
|
+
ab32[i+1] ^= enc[1];
|
432
|
+
ab32[i+2] ^= enc[2];
|
433
|
+
ab32[i+3] ^= enc[3];
|
434
|
+
|
435
|
+
if (!(++ctr[3])) ctr[2]++;
|
436
|
+
}
|
437
|
+
|
438
|
+
if (i < ab32.length)
|
439
|
+
{
|
440
|
+
var v = [0,0,0,0];
|
441
|
+
|
442
|
+
for (j = i; j < ab32.length; j++) v[j-i] = ab32[j];
|
443
|
+
|
444
|
+
mac[0] ^= v[0];
|
445
|
+
mac[1] ^= v[1];
|
446
|
+
mac[2] ^= v[2];
|
447
|
+
mac[3] ^= v[3];
|
448
|
+
mac = aes.encrypt(mac);
|
449
|
+
|
450
|
+
enc = aes.encrypt(ctr);
|
451
|
+
v[0] ^= enc[0];
|
452
|
+
v[1] ^= enc[1];
|
453
|
+
v[2] ^= enc[2];
|
454
|
+
v[3] ^= enc[3];
|
455
|
+
|
456
|
+
for (j = i; j < ab32.length; j++) ab32[j] = v[j-i];
|
457
|
+
}
|
458
|
+
|
459
|
+
ab.buffer = _a32_to_str(ab32,ab.buffer.length);
|
460
|
+
}
|
461
|
+
|
462
|
+
return mac;
|
463
|
+
}
|
464
|
+
|
465
|
+
function _str_to_a32(b)
|
466
|
+
{
|
467
|
+
var a = Array((b.length+3) >> 2);
|
468
|
+
|
469
|
+
if (typeof b == 'string')
|
470
|
+
{
|
471
|
+
for (var i = 0; i < b.length; i++)
|
472
|
+
a[i>>2] |= (b.charCodeAt(i) & 255) << (24-(i & 3)*8);
|
473
|
+
}
|
474
|
+
else
|
475
|
+
{
|
476
|
+
for (var i = 0; i < b.length; i++)
|
477
|
+
a[i>>2] |= b[i] << ((i & 3)*8);
|
478
|
+
}
|
479
|
+
|
480
|
+
return a;
|
481
|
+
}
|
482
|
+
|
483
|
+
function _a32_to_str(a,len)
|
484
|
+
{
|
485
|
+
var b = '';
|
486
|
+
|
487
|
+
for (var i = 0; i < len; i++)
|
488
|
+
b = b+String.fromCharCode((a[i>>2] >>> (24-(i & 3)*8)) & 255);
|
489
|
+
|
490
|
+
return b;
|
491
|
+
}
|
492
|
+
|
493
|
+
// decrypt ArrayBuffer in CTR mode, return MAC
|
494
|
+
function decrypt_ab_ctr(aes,ab,nonce,pos)
|
495
|
+
{
|
496
|
+
var ctr = [nonce[0],nonce[1],(pos/0x1000000000) >>> 0,(pos/0x10) >>> 0];
|
497
|
+
var mac = [ctr[0],ctr[1],ctr[0],ctr[1]];
|
498
|
+
|
499
|
+
var enc, len, i, j, v;
|
500
|
+
|
501
|
+
if (have_ab)
|
502
|
+
{
|
503
|
+
var data0, data1, data2, data3;
|
504
|
+
|
505
|
+
len = ab.buffer.byteLength-16; // @@@ -15?
|
506
|
+
|
507
|
+
var v = new DataView(ab.buffer);
|
508
|
+
|
509
|
+
for (i = 0; i < len; i += 16)
|
510
|
+
{
|
511
|
+
enc = aes.encrypt(ctr);
|
512
|
+
|
513
|
+
data0 = v.getUint32(i,false)^enc[0];
|
514
|
+
data1 = v.getUint32(i+4,false)^enc[1];
|
515
|
+
data2 = v.getUint32(i+8,false)^enc[2];
|
516
|
+
data3 = v.getUint32(i+12,false)^enc[3];
|
517
|
+
|
518
|
+
v.setUint32(i,data0,false);
|
519
|
+
v.setUint32(i+4,data1,false);
|
520
|
+
v.setUint32(i+8,data2,false);
|
521
|
+
v.setUint32(i+12,data3,false);
|
522
|
+
|
523
|
+
mac[0] ^= data0;
|
524
|
+
mac[1] ^= data1;
|
525
|
+
mac[2] ^= data2;
|
526
|
+
mac[3] ^= data3;
|
527
|
+
|
528
|
+
mac = aes.encrypt(mac);
|
529
|
+
|
530
|
+
if (!(++ctr[3])) ctr[2]++;
|
531
|
+
}
|
532
|
+
|
533
|
+
if (i < ab.buffer.byteLength)
|
534
|
+
{
|
535
|
+
var fullbuf = new Uint8Array(ab.buffer);
|
536
|
+
var tmpbuf = new ArrayBuffer(16);
|
537
|
+
var tmparray = new Uint8Array(tmpbuf);
|
538
|
+
|
539
|
+
tmparray.set(fullbuf.subarray(i));
|
540
|
+
|
541
|
+
v = new DataView(tmpbuf);
|
542
|
+
|
543
|
+
enc = aes.encrypt(ctr);
|
544
|
+
data0 = v.getUint32(0,false)^enc[0];
|
545
|
+
data1 = v.getUint32(4,false)^enc[1];
|
546
|
+
data2 = v.getUint32(8,false)^enc[2];
|
547
|
+
data3 = v.getUint32(12,false)^enc[3];
|
548
|
+
|
549
|
+
v.setUint32(0,data0,false);
|
550
|
+
v.setUint32(4,data1,false);
|
551
|
+
v.setUint32(8,data2,false);
|
552
|
+
v.setUint32(12,data3,false);
|
553
|
+
|
554
|
+
fullbuf.set(tmparray.subarray(0,j = fullbuf.length-i),i);
|
555
|
+
|
556
|
+
while (j < 16) tmparray[j++] = 0;
|
557
|
+
|
558
|
+
mac[0] ^= v.getUint32(0,false);
|
559
|
+
mac[1] ^= v.getUint32(4,false);
|
560
|
+
mac[2] ^= v.getUint32(8,false);
|
561
|
+
mac[3] ^= v.getUint32(12,false);
|
562
|
+
mac = aes.encrypt(mac);
|
563
|
+
}
|
564
|
+
}
|
565
|
+
else
|
566
|
+
{
|
567
|
+
var ab32 = _str_to_a32(ab.buffer);
|
568
|
+
len = ab32.length-3;
|
569
|
+
|
570
|
+
for (i = 0; i < len; i += 4)
|
571
|
+
{
|
572
|
+
enc = aes.encrypt(ctr);
|
573
|
+
mac[0] ^= (ab32[i] ^= enc[0]);
|
574
|
+
mac[1] ^= (ab32[i+1] ^= enc[1]);
|
575
|
+
mac[2] ^= (ab32[i+2] ^= enc[2]);
|
576
|
+
mac[3] ^= (ab32[i+3] ^= enc[3]);
|
577
|
+
mac = aes.encrypt(mac);
|
578
|
+
|
579
|
+
if (!(++ctr[3])) ctr[2]++;
|
580
|
+
}
|
581
|
+
|
582
|
+
if (i < ab32.length)
|
583
|
+
{
|
584
|
+
var v = [0,0,0,0];
|
585
|
+
|
586
|
+
for (j = i; j < ab32.length; j++) v[j-i] = ab32[j];
|
587
|
+
|
588
|
+
enc = aes.encrypt(ctr);
|
589
|
+
v[0] ^= enc[0];
|
590
|
+
v[1] ^= enc[1];
|
591
|
+
v[2] ^= enc[2];
|
592
|
+
v[3] ^= enc[3];
|
593
|
+
|
594
|
+
var j = ab.buffer.length & 15;
|
595
|
+
|
596
|
+
var m = _str_to_a32(Array(j+1).join(String.fromCharCode(255))+Array(17-j).join(String.fromCharCode(0)));
|
597
|
+
|
598
|
+
mac[0] ^= v[0] & m[0];
|
599
|
+
mac[1] ^= v[1] & m[1];
|
600
|
+
mac[2] ^= v[2] & m[2];
|
601
|
+
mac[3] ^= v[3] & m[3];
|
602
|
+
mac = aes.encrypt(mac);
|
603
|
+
|
604
|
+
for (j = i; j < ab32.length; j++) ab32[j] = v[j-i];
|
605
|
+
}
|
606
|
+
|
607
|
+
ab.buffer = _a32_to_str(ab32,ab.buffer.length);
|
608
|
+
}
|
609
|
+
|
610
|
+
return mac;
|
611
|
+
}
|
612
|
+
|
613
|
+
// encrypt ArrayBuffer in CBC mode (zero IV)
|
614
|
+
function encrypt_ab_cbc(cipher,ab)
|
615
|
+
{
|
616
|
+
if (have_ab)
|
617
|
+
{
|
618
|
+
var v = new DataView(ab);
|
619
|
+
var iv = [0,0,0,0], d = Array(4);
|
620
|
+
var i;
|
621
|
+
|
622
|
+
for (i = 0; i < ab.byteLength; i += 16)
|
623
|
+
{
|
624
|
+
d[0] = v.getUint32(i,false) ^ iv[0];
|
625
|
+
d[1] = v.getUint32(i+4,false) ^ iv[1];
|
626
|
+
d[2] = v.getUint32(i+8,false) ^ iv[2];
|
627
|
+
d[3] = v.getUint32(i+12,false) ^ iv[3];
|
628
|
+
|
629
|
+
iv = cipher.encrypt(d);
|
630
|
+
|
631
|
+
v.setUint32(i,iv[0],false);
|
632
|
+
v.setUint32(i+4,iv[1],false);
|
633
|
+
v.setUint32(i+8,iv[2],false);
|
634
|
+
v.setUint32(i+12,iv[3],false);
|
635
|
+
}
|
636
|
+
}
|
637
|
+
else
|
638
|
+
{
|
639
|
+
var ab32 = str_to_a32(ab.buffer);
|
640
|
+
var iv = [0,0,0,0], d = Array(4);
|
641
|
+
var i;
|
642
|
+
|
643
|
+
for (i = 0; i < ab32.length; i += 4)
|
644
|
+
{
|
645
|
+
d[0] = ab32[i] ^ iv[0];
|
646
|
+
d[1] = ab32[i+1] ^ iv[1];
|
647
|
+
d[2] = ab32[i+2] ^ iv[2];
|
648
|
+
d[3] = ab32[i+3] ^ iv[3];
|
649
|
+
|
650
|
+
iv = cipher.encrypt(d);
|
651
|
+
|
652
|
+
ab32[i] = iv[0];
|
653
|
+
ab32[i+1] = iv[1];
|
654
|
+
ab32[i+2] = iv[2];
|
655
|
+
ab32[i+3] = iv[3];
|
656
|
+
}
|
657
|
+
|
658
|
+
ab.buffer = a32_to_str(ab32);
|
659
|
+
}
|
660
|
+
}
|
661
|
+
|
662
|
+
// decrypt ArrayBuffer in CBC mode (zero IV)
|
663
|
+
function decrypt_ab_cbc(cipher,ab)
|
664
|
+
{
|
665
|
+
if (have_ab)
|
666
|
+
{
|
667
|
+
var v = new DataView(ab);
|
668
|
+
var iv = [0,0,0,0], d = Array(4), t = Array(4);
|
669
|
+
var i;
|
670
|
+
|
671
|
+
for (i = 0; i < ab.byteLength; i += 16)
|
672
|
+
{
|
673
|
+
d[0] = v.getUint32(i,false);
|
674
|
+
d[1] = v.getUint32(i+4,false);
|
675
|
+
d[2] = v.getUint32(i+8,false);
|
676
|
+
d[3] = v.getUint32(i+12,false);
|
677
|
+
t = d;
|
678
|
+
|
679
|
+
d = cipher.decrypt(d);
|
680
|
+
|
681
|
+
v.setUint32(i,d[0] ^ iv[0],false);
|
682
|
+
v.setUint32(i+4,d[1] ^ iv[1],false);
|
683
|
+
v.setUint32(i+8,d[2] ^ iv[2],false);
|
684
|
+
v.setUint32(i+12,d[3] ^ iv[3],false);
|
685
|
+
iv = t;
|
686
|
+
}
|
687
|
+
}
|
688
|
+
else
|
689
|
+
{
|
690
|
+
var ab32 = str_to_a32(ab.buffer);
|
691
|
+
var iv = [0,0,0,0], d = Array(4), t = Array(4);
|
692
|
+
var i;
|
693
|
+
|
694
|
+
for (i = 0; i < ab32.length; i += 4)
|
695
|
+
{
|
696
|
+
d[0] = ab32[i];
|
697
|
+
d[1] = ab32[i+1];
|
698
|
+
d[2] = ab32[i+2];
|
699
|
+
d[3] = ab32[i+3];
|
700
|
+
t = d;
|
701
|
+
|
702
|
+
d = cipher.decrypt(d);
|
703
|
+
|
704
|
+
ab32[i] = d[0] ^ iv[0];
|
705
|
+
ab32[i+1] = d[1] ^ iv[1];
|
706
|
+
ab32[i+2] = d[2] ^ iv[2];
|
707
|
+
ab32[i+3] = d[3] ^ iv[3];
|
708
|
+
iv = t;
|
709
|
+
}
|
710
|
+
|
711
|
+
ab.buffer = a32_to_str(ab32);
|
712
|
+
}
|
713
|
+
}
|
714
|
+
|
715
|
+
// encrypt/decrypt 4- or 8-element 32-bit integer array
|
716
|
+
function encrypt_key(cipher,a)
|
717
|
+
{
|
718
|
+
if (a.length == 4) return cipher.encrypt(a);
|
719
|
+
|
720
|
+
var x = [];
|
721
|
+
for (var i = 0; i < a.length; i += 4) x = x.concat(cipher.encrypt([a[i],a[i+1],a[i+2],a[i+3]]));
|
722
|
+
return x;
|
723
|
+
}
|
724
|
+
|
725
|
+
function decrypt_key(cipher,a)
|
726
|
+
{
|
727
|
+
if (a.length == 4) return cipher.decrypt(a);
|
728
|
+
|
729
|
+
var x = [];
|
730
|
+
for (var i = 0; i < a.length; i += 4) x = x.concat(cipher.decrypt([a[i],a[i+1],a[i+2],a[i+3]]));
|
731
|
+
return x;
|
732
|
+
}
|
733
|
+
|
734
|
+
// generate attributes block using AES-CBC with MEGA canary
|
735
|
+
// attr = Object, key = [] (four-word random key will be generated) or Array(8) (lower four words will be used)
|
736
|
+
// returns [ArrayBuffer data,Array key]
|
737
|
+
function enc_attr(attr,key)
|
738
|
+
{
|
739
|
+
var aes;
|
740
|
+
var ab;
|
741
|
+
var b;
|
742
|
+
|
743
|
+
ab = str_to_ab('MEGA'+to8(JSON.stringify(attr)));
|
744
|
+
|
745
|
+
// if no key supplied, generate a random one
|
746
|
+
if (!key.length) for (i = 4; i--; ) key[i] = rand(0x100000000);
|
747
|
+
|
748
|
+
aes = new sjcl.cipher.aes([key[0]^key[4],key[1]^key[5],key[2]^key[6],key[3]^key[7]]);
|
749
|
+
|
750
|
+
encrypt_ab_cbc(aes,ab);
|
751
|
+
|
752
|
+
return [ab,key];
|
753
|
+
}
|
754
|
+
|
755
|
+
// decrypt attributes block using AES-CBC, check for MEGA canary
|
756
|
+
// attr = ab, key as with enc_attr
|
757
|
+
// returns [Object] or false
|
758
|
+
function dec_attr(attr,key)
|
759
|
+
{
|
760
|
+
var aes;
|
761
|
+
var b;
|
762
|
+
|
763
|
+
aes = new sjcl.cipher.aes([key[0]^key[4],key[1]^key[5],key[2]^key[6],key[3]^key[7]]);
|
764
|
+
decrypt_ab_cbc(aes,attr);
|
765
|
+
|
766
|
+
b = ab_to_str_depad(attr);
|
767
|
+
|
768
|
+
if (b.substr(0,6) != 'MEGA{"') return false;
|
769
|
+
|
770
|
+
// @@@ protect against syntax errors
|
771
|
+
try {
|
772
|
+
return JSON.parse(from8(b.substr(4)));
|
773
|
+
} catch (e) {
|
774
|
+
return { n : 'MALFORMED_ATTRIBUTES' };
|
775
|
+
}
|
776
|
+
}
|
777
|
+
|
778
|
+
function to8(unicode)
|
779
|
+
{
|
780
|
+
return unescape(encodeURIComponent(unicode));
|
781
|
+
}
|
782
|
+
|
783
|
+
function from8(utf8)
|
784
|
+
{
|
785
|
+
return decodeURIComponent(escape(utf8));
|
786
|
+
}
|
787
|
+
|
788
|
+
function getxhr()
|
789
|
+
{
|
790
|
+
return (typeof XDomainRequest != 'undefined' && typeof ArrayBuffer == 'undefined') ? new XDomainRequest() : new XMLHttpRequest();
|
791
|
+
}
|
792
|
+
|
793
|
+
// API command queueing
|
794
|
+
// All commands are executed in sequence, with no overlap
|
795
|
+
// @@@ user warning after backoff > 1000
|
796
|
+
|
797
|
+
var apiq = new Array;
|
798
|
+
var apiqtimer = false;
|
799
|
+
var apixhr = getxhr();
|
800
|
+
|
801
|
+
function api_req(req,params)
|
802
|
+
{
|
803
|
+
apiq.push([typeof req == 'string' ? req : to8(JSON.stringify(req)),params,seqno++,0]);
|
804
|
+
|
805
|
+
if (apiq.length == 1) api_proc();
|
806
|
+
}
|
807
|
+
|
808
|
+
// execute first pending event
|
809
|
+
function api_proc()
|
810
|
+
{
|
811
|
+
if (apiqtimer)
|
812
|
+
{
|
813
|
+
// delete timer (should not ever happen)
|
814
|
+
clearTimeout(apiqtimer);
|
815
|
+
apiqtimer = false;
|
816
|
+
}
|
817
|
+
|
818
|
+
if (apixhr.readyState && apixhr.readyState != apixhr.DONE)
|
819
|
+
{
|
820
|
+
// wait for apixhr to get ready
|
821
|
+
if (d) console.log("XHR not in DONE state: " + apixhr.readyState);
|
822
|
+
apiqtimer = setTimeout(api_proc,1000);
|
823
|
+
return;
|
824
|
+
}
|
825
|
+
|
826
|
+
// no more commands pending?
|
827
|
+
if (!apiq.length) return;
|
828
|
+
|
829
|
+
// request ready for (re)execution: execute
|
830
|
+
apixhr = getxhr();
|
831
|
+
|
832
|
+
|
833
|
+
apixhr.onerror = function()
|
834
|
+
{
|
835
|
+
if (d) console.log("API request error - retrying");
|
836
|
+
api_result(-3);
|
837
|
+
}
|
838
|
+
|
839
|
+
apixhr.onload = function()
|
840
|
+
{
|
841
|
+
var t;
|
842
|
+
|
843
|
+
if (this.responseText) this.response = this.responseText;
|
844
|
+
|
845
|
+
if (d) console.log('API response: ' + this.response);
|
846
|
+
|
847
|
+
try {
|
848
|
+
t = JSON.parse(from8(this.response));
|
849
|
+
} catch (e) {
|
850
|
+
// bogus response, requeue
|
851
|
+
console.log("Bad JSON data in response: " + this.response);
|
852
|
+
t = -3;
|
853
|
+
}
|
854
|
+
|
855
|
+
api_result(t);
|
856
|
+
}
|
857
|
+
|
858
|
+
if (d) console.log("Making API request: " + apiq[0][0]);
|
859
|
+
apixhr.open('POST', (apiq[0][0].substr(0,4) == 'https') ? apiq[0][0] : (apipath + (apiq[0][0].substr(0,1) == '[' ? ('cs?id=' + apiq[0][2]) : apiq[0][0]) + (u_sid ? ('&sid=' + u_sid) : '')), true);
|
860
|
+
apixhr.send(apiq[0][0]);
|
861
|
+
}
|
862
|
+
|
863
|
+
function api_result(res)
|
864
|
+
{
|
865
|
+
if (res === -3)
|
866
|
+
{
|
867
|
+
// exponential backoff
|
868
|
+
if (apiq[0][3]) apiq[0][3] *= 2;
|
869
|
+
else apiq[0][3] = 125;
|
870
|
+
|
871
|
+
if (d) console.log('Temporary error (' + res + ') - retrying after: ' + (apiq[0][3]/1000));
|
872
|
+
|
873
|
+
apiqtimer = setTimeout(api_proc,apiq[0][3]);
|
874
|
+
}
|
875
|
+
else
|
876
|
+
{
|
877
|
+
if (apiq[0][1]) apiq[0][1].callback(res,apiq[0][1]);
|
878
|
+
apiq.shift();
|
879
|
+
api_proc();
|
880
|
+
}
|
881
|
+
}
|
882
|
+
|
883
|
+
// calls execsc() with server-client requests received
|
884
|
+
function getsc()
|
885
|
+
{
|
886
|
+
ctx = {
|
887
|
+
callback : function(res,ctx)
|
888
|
+
{
|
889
|
+
if (res.w)
|
890
|
+
{
|
891
|
+
waiturl = res.w;
|
892
|
+
|
893
|
+
if (waitbackoff > 1000) setTimeout(waitsc,waitbackoff);
|
894
|
+
else waitsc();
|
895
|
+
}
|
896
|
+
else
|
897
|
+
{
|
898
|
+
if (res.sn) maxaction = res.sn;
|
899
|
+
execsc(res.a);
|
900
|
+
}
|
901
|
+
}
|
902
|
+
};
|
903
|
+
|
904
|
+
api_req('sc?sn=' + maxaction + '&ssl=1',ctx);
|
905
|
+
}
|
906
|
+
|
907
|
+
var waiturl;
|
908
|
+
var waitxhr = getxhr();
|
909
|
+
var waitbackoff = 125;
|
910
|
+
var waitbegin;
|
911
|
+
|
912
|
+
function waitsc()
|
913
|
+
{
|
914
|
+
if (waitxhr.readyState != apixhr.DONE) waitxhr = undefined;
|
915
|
+
|
916
|
+
if (!waitxhr) waitxhr = getxhr();
|
917
|
+
|
918
|
+
waitxhr.onerror = function()
|
919
|
+
{
|
920
|
+
if (d) console.log("Error while waiting - retrying, backoff: " + waitbackoff);
|
921
|
+
getsc();
|
922
|
+
}
|
923
|
+
|
924
|
+
waitxhr.onload = function()
|
925
|
+
{
|
926
|
+
var t = new Date().getTime()-waitbegin;
|
927
|
+
if (t < 1000) waitbackoff += waitbackoff;
|
928
|
+
else waitbackoff = 125;
|
929
|
+
getsc();
|
930
|
+
}
|
931
|
+
|
932
|
+
waitbegin = new Date().getTime();
|
933
|
+
waitxhr.open('POST',waiturl,true);
|
934
|
+
waitxhr.send();
|
935
|
+
}
|
936
|
+
|
937
|
+
function api_create_u_k()
|
938
|
+
{
|
939
|
+
u_k = Array(4); // static master key, will be stored at the server side encrypted with the master pw
|
940
|
+
|
941
|
+
for (var i = 4; i--; ) u_k[i] = rand(0x100000000);
|
942
|
+
}
|
943
|
+
|
944
|
+
// If the user triggers an action that requires an account, but hasn't logged in,
|
945
|
+
// we create an anonymous preliminary account. Returns userhandle and passwordkey for permanent storage.
|
946
|
+
function api_createuser(ctx,invitecode,invitename,uh)
|
947
|
+
{
|
948
|
+
var i;
|
949
|
+
var ssc = Array(4); // session self challenge, will be used to verify password
|
950
|
+
var req, res;
|
951
|
+
|
952
|
+
if (!ctx.passwordkey)
|
953
|
+
{
|
954
|
+
ctx.passwordkey = Array(4);
|
955
|
+
for (i = 4; i--; ) ctx.passwordkey[i] = rand(0x100000000);
|
956
|
+
}
|
957
|
+
|
958
|
+
if (!u_k) api_create_u_k();
|
959
|
+
|
960
|
+
for (i = 4; i--; ) ssc[i] = rand(0x100000000);
|
961
|
+
|
962
|
+
if (d) console.log("api_createuser - masterkey: " + u_k + " passwordkey: " + ctx.passwordkey);
|
963
|
+
|
964
|
+
req = { a : 'up',
|
965
|
+
k : a32_to_base64(encrypt_key(new sjcl.cipher.aes(ctx.passwordkey),u_k)),
|
966
|
+
ts : base64urlencode(a32_to_str(ssc) + a32_to_str(encrypt_key(new sjcl.cipher.aes(u_k),ssc))) };
|
967
|
+
|
968
|
+
if (invitecode)
|
969
|
+
{
|
970
|
+
req.uh = uh;
|
971
|
+
req.ic = invitecode;
|
972
|
+
req.name = invitename;
|
973
|
+
}
|
974
|
+
|
975
|
+
//if (confirmcode) req.c = confirmcode;
|
976
|
+
if (d) console.log("Storing key: " + req.k);
|
977
|
+
|
978
|
+
api_req([req],ctx);
|
979
|
+
}
|
980
|
+
|
981
|
+
function api_checkconfirmcode(ctx,c)
|
982
|
+
{
|
983
|
+
res = api_req([{ a : 'uc', c : c }],ctx);
|
984
|
+
}
|
985
|
+
|
986
|
+
// We query the sid using the supplied user handle (or entered email address, if already attached)
|
987
|
+
// and check the supplied password key.
|
988
|
+
// Returns [decrypted master key,verified session ID(,RSA private key)] or false if API error or
|
989
|
+
// supplied information incorrect
|
990
|
+
function api_getsid(ctx,user,passwordkey,hash)
|
991
|
+
{
|
992
|
+
ctx.callback = api_getsid2;
|
993
|
+
ctx.passwordkey = passwordkey;
|
994
|
+
|
995
|
+
api_req([{ a : 'us', user : user, uh : hash }],ctx);
|
996
|
+
}
|
997
|
+
|
998
|
+
function api_getsid2(res,ctx)
|
999
|
+
{
|
1000
|
+
console.log(new Date().getTime());
|
1001
|
+
|
1002
|
+
var t, k;
|
1003
|
+
var r = false;
|
1004
|
+
|
1005
|
+
if (typeof res == 'object')
|
1006
|
+
{
|
1007
|
+
var aes = new sjcl.cipher.aes(ctx.passwordkey);
|
1008
|
+
|
1009
|
+
// decrypt master key
|
1010
|
+
if (typeof res[0].k == 'string')
|
1011
|
+
{
|
1012
|
+
k = base64_to_a32(res[0].k);
|
1013
|
+
|
1014
|
+
if (k.length == 4)
|
1015
|
+
{
|
1016
|
+
k = decrypt_key(aes,k);
|
1017
|
+
|
1018
|
+
aes = new sjcl.cipher.aes(k);
|
1019
|
+
|
1020
|
+
if (typeof res[0].tsid == 'string')
|
1021
|
+
{
|
1022
|
+
t = base64urldecode(res[0].tsid);
|
1023
|
+
if (a32_to_str(encrypt_key(aes,str_to_a32(t.substr(0,16)))) == t.substr(-16)) r = [k,res[0].tsid];
|
1024
|
+
}
|
1025
|
+
else if (typeof res[0].csid == 'string')
|
1026
|
+
{
|
1027
|
+
var t = mpi2b(base64urldecode(res[0].csid));
|
1028
|
+
|
1029
|
+
var privk = a32_to_str(decrypt_key(aes,base64_to_a32(res[0].privk)));
|
1030
|
+
|
1031
|
+
var rsa_privk = Array(4);
|
1032
|
+
|
1033
|
+
// decompose private key
|
1034
|
+
for (var i = 0; i < 4; i++)
|
1035
|
+
{
|
1036
|
+
var l = ((privk.charCodeAt(0)*256+privk.charCodeAt(1)+7)>>3)+2;
|
1037
|
+
|
1038
|
+
rsa_privk[i] = mpi2b(privk.substr(0,l));
|
1039
|
+
if (typeof rsa_privk[i] == 'number') break;
|
1040
|
+
privk = privk.substr(l);
|
1041
|
+
}
|
1042
|
+
|
1043
|
+
// check format
|
1044
|
+
if (i == 4 && privk.length < 16)
|
1045
|
+
{
|
1046
|
+
// @@@ check remaining padding for added early wrong password detection likelihood
|
1047
|
+
r = [k,base64urlencode(b2s(RSAdecrypt(t,rsa_privk[2],rsa_privk[0],rsa_privk[1],rsa_privk[3])).substr(0,43)),rsa_privk];
|
1048
|
+
}
|
1049
|
+
}
|
1050
|
+
}
|
1051
|
+
}
|
1052
|
+
}
|
1053
|
+
|
1054
|
+
console.log(new Date().getTime());
|
1055
|
+
|
1056
|
+
ctx.result(ctx,r);
|
1057
|
+
}
|
1058
|
+
|
1059
|
+
// We call ug using the sid from setsid() and the user's master password to obtain the master key (and other credentials)
|
1060
|
+
// Returns user credentials (.k being the decrypted master key) or false in case of an error.
|
1061
|
+
function api_getuser(ctx)
|
1062
|
+
{
|
1063
|
+
api_req([{ a : 'ug' }],ctx);
|
1064
|
+
}
|
1065
|
+
|
1066
|
+
// User must be logged in, sid and passwordkey must be valid
|
1067
|
+
// return values:
|
1068
|
+
// 2 - old & new passwords are the same post-preparation
|
1069
|
+
// 1 - old password incorrect
|
1070
|
+
// userhandle - success
|
1071
|
+
// false - processing error
|
1072
|
+
// other negative values - API error
|
1073
|
+
function api_changepw(ctx,passwordkey,masterkey,oldpw,newpw,email)
|
1074
|
+
{
|
1075
|
+
var req, res;
|
1076
|
+
var oldkey;
|
1077
|
+
|
1078
|
+
var newkey = prepare_key_pw(newpw);
|
1079
|
+
|
1080
|
+
if (oldpw !== false)
|
1081
|
+
{
|
1082
|
+
var oldkey = prepare_key_pw(oldpw);
|
1083
|
+
|
1084
|
+
// quick check of old pw
|
1085
|
+
if (oldkey[0] != passwordkey[0]
|
1086
|
+
|| oldkey[1] != passwordkey[1]
|
1087
|
+
|| oldkey[2] != passwordkey[2]
|
1088
|
+
|| oldkey[3] != passwordkey[3]) return 1;
|
1089
|
+
|
1090
|
+
if (oldkey[0] == newkey[0]
|
1091
|
+
&& oldkey[1] == newkey[1]
|
1092
|
+
&& oldkey[2] == newkey[2]
|
1093
|
+
&& oldkey[3] == newkey[3]) return 2;
|
1094
|
+
}
|
1095
|
+
|
1096
|
+
var aes = new sjcl.cipher.aes(newkey);
|
1097
|
+
|
1098
|
+
// encrypt masterkey with the new password
|
1099
|
+
var cmasterkey = encrypt_key(aes,masterkey);
|
1100
|
+
|
1101
|
+
req = { a : 'up',
|
1102
|
+
k : a32_to_base64(cmasterkey) };
|
1103
|
+
|
1104
|
+
if (email.length) req.email = email;
|
1105
|
+
|
1106
|
+
api_req([req],ctx);
|
1107
|
+
}
|
1108
|
+
|
1109
|
+
function stringhash(s,aes)
|
1110
|
+
{
|
1111
|
+
var s32 = str_to_a32(s);
|
1112
|
+
var h32 = [0,0,0,0];
|
1113
|
+
|
1114
|
+
for (i = 0; i < s32.length; i++) h32[i&3] ^= s32[i];
|
1115
|
+
|
1116
|
+
for (i = 16384; i--; ) h32 = aes.encrypt(h32);
|
1117
|
+
|
1118
|
+
return a32_to_base64([h32[0],h32[2]]);
|
1119
|
+
}
|
1120
|
+
|
1121
|
+
// Update user
|
1122
|
+
// Can also be used to set keys and to confirm accounts (.c)
|
1123
|
+
function api_updateuser(ctx,newuser)
|
1124
|
+
{
|
1125
|
+
newuser.a = 'up';
|
1126
|
+
|
1127
|
+
res = api_req([newuser],ctx);
|
1128
|
+
}
|
1129
|
+
|
1130
|
+
var u_pubkeys = new Object;
|
1131
|
+
|
1132
|
+
// Encrypt data to a user's public key
|
1133
|
+
// Returns false in case no public key is available
|
1134
|
+
function api_cachepubkey(ctx,user)
|
1135
|
+
{
|
1136
|
+
ctx.user = user;
|
1137
|
+
ctx.callback = api_cachepubkey2;
|
1138
|
+
|
1139
|
+
if (u_pubkeys[user]) ctx.cachepubkeycomplete(ctx,u_pubkeys[user]);
|
1140
|
+
else api_req([{ a : 'uk', u : user }],ctx);
|
1141
|
+
}
|
1142
|
+
|
1143
|
+
function api_cachepubkey2(res,ctx)
|
1144
|
+
{
|
1145
|
+
if (typeof res == 'object' && typeof res[0].pubk == 'string')
|
1146
|
+
{
|
1147
|
+
var spubkey = base64urldecode(res[0].pubk);
|
1148
|
+
var keylen = spubkey.charCodeAt(0)*256+spubkey.charCodeAt(1);
|
1149
|
+
var pubkey = Array(3);
|
1150
|
+
var i;
|
1151
|
+
|
1152
|
+
// decompose public key
|
1153
|
+
for (i = 0; i < 2; i++)
|
1154
|
+
{
|
1155
|
+
var l = ((spubkey.charCodeAt(0)*256+spubkey.charCodeAt(1)+7)>>3)+2;
|
1156
|
+
|
1157
|
+
pubkey[i] = mpi2b(spubkey.substr(0,l));
|
1158
|
+
if (typeof pubkey[i] == 'number') break;
|
1159
|
+
spubkey = spubkey.substr(l);
|
1160
|
+
}
|
1161
|
+
|
1162
|
+
// check format
|
1163
|
+
if (i == 2 && spubkey.length < 16)
|
1164
|
+
{
|
1165
|
+
pubkey[2] = keylen;
|
1166
|
+
u_pubkeys[ctx.user] = pubkey;
|
1167
|
+
}
|
1168
|
+
else pubkey = false;
|
1169
|
+
}
|
1170
|
+
|
1171
|
+
ctx.cachepubkeycomplete(ctx,pubkey);
|
1172
|
+
}
|
1173
|
+
|
1174
|
+
function encryptto(user,data)
|
1175
|
+
{
|
1176
|
+
var i, data;
|
1177
|
+
var pubkey;
|
1178
|
+
|
1179
|
+
if (pubkey = u_pubkeys[user])
|
1180
|
+
{
|
1181
|
+
// random padding
|
1182
|
+
for (i = (pubkey[2]>>3)-1-data.length; i-- > 0; ) data = data+String.fromCharCode(rand(256));
|
1183
|
+
|
1184
|
+
i = data.length*8;
|
1185
|
+
data = String.fromCharCode(i >> 8) + String.fromCharCode(i & 255) + data;
|
1186
|
+
|
1187
|
+
return b2mpi(RSAencrypt(mpi2b(data),pubkey[1],pubkey[0]));
|
1188
|
+
}
|
1189
|
+
|
1190
|
+
return false;
|
1191
|
+
}
|
1192
|
+
|
1193
|
+
var u_sharekeys = {};
|
1194
|
+
var u_nodekeys = {};
|
1195
|
+
|
1196
|
+
// u_nodekeys must be set for all sharenodes
|
1197
|
+
// Add/cancel share(s) to a set of users or email addresses
|
1198
|
+
// targets is an array of {u,r} - if no r given, cancel share
|
1199
|
+
// If no sharekey known, tentatively generates one and encrypts
|
1200
|
+
// everything to it. In case of a mismatch, the API call returns
|
1201
|
+
// an error, and the whole operation gets repeated (exceedingly
|
1202
|
+
// rare race condition).
|
1203
|
+
function api_setshare1(node,targets,sharenodes,ctx)
|
1204
|
+
{
|
1205
|
+
var i, j, n, nk, sharekey, ssharekey;
|
1206
|
+
var req, res;
|
1207
|
+
var newkey = false;
|
1208
|
+
|
1209
|
+
req = { a : 's',
|
1210
|
+
n : node,
|
1211
|
+
s : []
|
1212
|
+
};
|
1213
|
+
|
1214
|
+
ctx.sharenodes = sharenodes;
|
1215
|
+
ctx.targets = targets;
|
1216
|
+
|
1217
|
+
// we only need to generate a key if one or more shares are added
|
1218
|
+
for (i = targets.length; i--; )
|
1219
|
+
{
|
1220
|
+
if (typeof targets[i].r == 'undefined')
|
1221
|
+
{
|
1222
|
+
// share cancellation
|
1223
|
+
req.s.push({ u : targets[i].u });
|
1224
|
+
}
|
1225
|
+
else
|
1226
|
+
{
|
1227
|
+
if (!req.ok)
|
1228
|
+
{
|
1229
|
+
if (u_sharekeys[node]) sharekey = u_sharekeys[node];
|
1230
|
+
else
|
1231
|
+
{
|
1232
|
+
sharekey = Array(4);
|
1233
|
+
for (j = 4; j--; ) sharekey[j] = rand(0x100000000);
|
1234
|
+
u_sharekeys[node] = sharekey;
|
1235
|
+
newkey = true;
|
1236
|
+
}
|
1237
|
+
|
1238
|
+
req.ok = a32_to_base64(encrypt_key(u_k_aes,sharekey));
|
1239
|
+
req.ha = crypto_handleauth(node);
|
1240
|
+
ssharekey = a32_to_str(sharekey);
|
1241
|
+
}
|
1242
|
+
}
|
1243
|
+
}
|
1244
|
+
|
1245
|
+
u_sharekeys[node] = sharekey;
|
1246
|
+
|
1247
|
+
if (newkey) req.cr = crypto_makecr(sharenodes,[node],true);
|
1248
|
+
|
1249
|
+
ctx.tried = -1;
|
1250
|
+
ctx.ssharekey = ssharekey;
|
1251
|
+
ctx.req = req;
|
1252
|
+
ctx.i = 0;
|
1253
|
+
ctx.node = node;
|
1254
|
+
ctx.targets = targets;
|
1255
|
+
ctx.sharenodes = sharenodes;
|
1256
|
+
|
1257
|
+
ctx.callback = function(res,ctx)
|
1258
|
+
{
|
1259
|
+
var pubkey;
|
1260
|
+
var i;
|
1261
|
+
|
1262
|
+
if (typeof res == 'object' && typeof res[0] == 'object')
|
1263
|
+
{
|
1264
|
+
if (typeof res[0].pubk == 'string') u_pubkeys[ctx.targets[ctx.i].u] = crypto_decodepubkey(res[0].pubk);
|
1265
|
+
else if (res[0].ok)
|
1266
|
+
{
|
1267
|
+
u_sharekeys[node] = decrypt_key(u_k_aes,base64_to_a32(res[0].ok));
|
1268
|
+
return api_setshare(ctx.node,ctx.targets,ctx.sharenodes,ctx);
|
1269
|
+
}
|
1270
|
+
}
|
1271
|
+
|
1272
|
+
if (ctx.i == ctx.targets.length) ctx.done(ctx);
|
1273
|
+
else if (!(pubkey = u_pubkeys[ctx.targets[ctx.i].u]) && ctx.tried < ctx.i)
|
1274
|
+
{
|
1275
|
+
ctx.tried = ctx.i;
|
1276
|
+
|
1277
|
+
// no public key cached for this user: get it!
|
1278
|
+
api_req([{ a : 'uk', u : ctx.targets[ctx.i].u }],ctx);
|
1279
|
+
}
|
1280
|
+
else
|
1281
|
+
{
|
1282
|
+
n = false;
|
1283
|
+
|
1284
|
+
if (pubkey)
|
1285
|
+
{
|
1286
|
+
// pubkey found: encrypt share key to it
|
1287
|
+
n = crypto_rsaencrypt(pubkey,ctx.ssharekey);
|
1288
|
+
}
|
1289
|
+
|
1290
|
+
if (n) ctx.req.s.push({ u : ctx.targets[ctx.i].u, r : ctx.targets[ctx.i].r, k : base64urlencode(n) });
|
1291
|
+
else ctx.req.s.push({ u : ctx.targets[ctx.i].u, r : ctx.targets[ctx.i].r });
|
1292
|
+
|
1293
|
+
ctx.i++;
|
1294
|
+
|
1295
|
+
ctx.callback(false,ctx);
|
1296
|
+
}
|
1297
|
+
}
|
1298
|
+
|
1299
|
+
ctx.callback(false,ctx);
|
1300
|
+
}
|
1301
|
+
|
1302
|
+
function api_setshare2(res,node)
|
1303
|
+
{
|
1304
|
+
if (res[0].ok) u_sharekeys[node] = decrypt_key(u_k_aes,base64_to_a32(res[0].ok));
|
1305
|
+
}
|
1306
|
+
|
1307
|
+
function api_setrsa(privk,pubk)
|
1308
|
+
{
|
1309
|
+
var t, i;
|
1310
|
+
|
1311
|
+
for (t = '', i = 0; i < privk.length; i++) t = t+b2mpi(privk[i]);
|
1312
|
+
|
1313
|
+
for (i = (-t.length)&15; i--; ) t = t + String.fromCharCode(rand(256));
|
1314
|
+
|
1315
|
+
ctx = { callback : function(res,ctx)
|
1316
|
+
{
|
1317
|
+
if (d) console.log("RSA key put result=" + res);
|
1318
|
+
|
1319
|
+
u_privk = ctx.privk;
|
1320
|
+
u_storage.privk = JSON.stringify(u_privk);
|
1321
|
+
u_type = 3;
|
1322
|
+
|
1323
|
+
ui_keycomplete();
|
1324
|
+
},
|
1325
|
+
privk : privk
|
1326
|
+
};
|
1327
|
+
|
1328
|
+
api_req([{ a : 'up', privk : a32_to_base64(encrypt_key(u_k_aes,str_to_a32(t))), pubk : base64urlencode(b2mpi(pubk[0])+b2mpi(pubk[1])) }],ctx);
|
1329
|
+
}
|
1330
|
+
|
1331
|
+
function crypto_handleauth(h)
|
1332
|
+
{
|
1333
|
+
return a32_to_base64(encrypt_key(u_k_aes,str_to_a32(h+h)));
|
1334
|
+
}
|
1335
|
+
|
1336
|
+
function crypto_decodepubkey(pubk)
|
1337
|
+
{
|
1338
|
+
var i;
|
1339
|
+
|
1340
|
+
var spubkey = base64urldecode(pubk);
|
1341
|
+
|
1342
|
+
var keylen = spubkey.charCodeAt(0)*256+spubkey.charCodeAt(1);
|
1343
|
+
|
1344
|
+
var pubkey = Array(3);
|
1345
|
+
|
1346
|
+
// decompose public key
|
1347
|
+
for (i = 0; i < 2; i++)
|
1348
|
+
{
|
1349
|
+
var l = ((spubkey.charCodeAt(0)*256+spubkey.charCodeAt(1)+7)>>3)+2;
|
1350
|
+
|
1351
|
+
pubkey[i] = mpi2b(spubkey.substr(0,l));
|
1352
|
+
if (typeof pubkey[i] == 'number') break;
|
1353
|
+
spubkey = spubkey.substr(l);
|
1354
|
+
}
|
1355
|
+
|
1356
|
+
// check format
|
1357
|
+
if (i == 2 && spubkey.length < 16)
|
1358
|
+
{
|
1359
|
+
pubkey[2] = keylen;
|
1360
|
+
return pubkey;
|
1361
|
+
}
|
1362
|
+
return false;
|
1363
|
+
}
|
1364
|
+
|
1365
|
+
function crypto_rsaencrypt(pubkey,data)
|
1366
|
+
{
|
1367
|
+
var i;
|
1368
|
+
|
1369
|
+
// random padding
|
1370
|
+
for (i = (pubkey[2]>>3)-1-data.length; i-- > 0; ) data = data+String.fromCharCode(rand(256));
|
1371
|
+
|
1372
|
+
i = data.length*8;
|
1373
|
+
data = String.fromCharCode(i >> 8) + String.fromCharCode(i & 255) + data;
|
1374
|
+
|
1375
|
+
return b2mpi(RSAencrypt(mpi2b(data),pubkey[1],pubkey[0]));
|
1376
|
+
}
|
1377
|
+
|
1378
|
+
// Complete upload
|
1379
|
+
// We construct a special node put command that uses the upload token
|
1380
|
+
// as the source handle
|
1381
|
+
function api_completeupload(t,ut,path,n,k,ctx)
|
1382
|
+
{
|
1383
|
+
ctx2 = { callback : api_completeupload2, t : base64urlencode(t), path : path, n : n, k : k, ctx : ctx };
|
1384
|
+
|
1385
|
+
api_completeupload2(ctx2,ut);
|
1386
|
+
}
|
1387
|
+
|
1388
|
+
function api_completeupload2(ctx,ut)
|
1389
|
+
{
|
1390
|
+
var p;
|
1391
|
+
|
1392
|
+
if (ctx.path && ctx.path != ctx.n && (p = ctx.path.indexOf('/')) > 0)
|
1393
|
+
{
|
1394
|
+
var pc = ctx.path.substr(0,p);
|
1395
|
+
|
1396
|
+
ctx.path = ctx.path.substr(p+1);
|
1397
|
+
|
1398
|
+
fm_requestfolderid(ut,pc,ctx);
|
1399
|
+
}
|
1400
|
+
else
|
1401
|
+
{
|
1402
|
+
a = { n : ctx.n };
|
1403
|
+
|
1404
|
+
if (d) console.log(ctx.k);
|
1405
|
+
|
1406
|
+
var ea = enc_attr(a,ctx.k);
|
1407
|
+
|
1408
|
+
if (d) console.log(ea);
|
1409
|
+
|
1410
|
+
var req = { a : 'p',
|
1411
|
+
t : ut,
|
1412
|
+
n : [{ h : ctx.t, t : 0, a : ab_to_base64(ea[0]), k : a32_to_base64(encrypt_key(u_k_aes,ctx.k)) }]
|
1413
|
+
};
|
1414
|
+
|
1415
|
+
|
1416
|
+
if (ut)
|
1417
|
+
{
|
1418
|
+
// a target has been supplied: encrypt to all relevant shares
|
1419
|
+
var sn = fm_getsharenodes(ut);
|
1420
|
+
|
1421
|
+
if (sn.length)
|
1422
|
+
{
|
1423
|
+
req.cr = crypto_makecr([ctx.k],sn,false);
|
1424
|
+
req.cr[1][0] = ctx.t;
|
1425
|
+
}
|
1426
|
+
}
|
1427
|
+
|
1428
|
+
api_req([req],ctx.ctx);
|
1429
|
+
}
|
1430
|
+
}
|
1431
|
+
|
1432
|
+
// generate crypto request response for the given nodes/shares matrix
|
1433
|
+
function crypto_makecr(source,shares,source_is_nodes)
|
1434
|
+
{
|
1435
|
+
var i, j, n;
|
1436
|
+
var cr = [shares,[],[]];
|
1437
|
+
var aes;
|
1438
|
+
|
1439
|
+
// if we have node handles, include in cr - otherwise, we have node keys
|
1440
|
+
if (source_is_nodes) cr[1] = source;
|
1441
|
+
|
1442
|
+
// TODO: optimize - keep track of pre-existing/sent keys, only send new ones
|
1443
|
+
for (i = shares.length; i--; )
|
1444
|
+
{
|
1445
|
+
if (u_sharekeys[shares[i]])
|
1446
|
+
{
|
1447
|
+
aes = new sjcl.cipher.aes(u_sharekeys[shares[i]]);
|
1448
|
+
|
1449
|
+
for (j = source.length; j--; )
|
1450
|
+
{
|
1451
|
+
if (source_is_nodes ? (nk = u_nodekeys[source[j]]) : (nk = source[j]))
|
1452
|
+
{
|
1453
|
+
if (nk.length == 8 || nk.length == 4) cr[2].push(i,j,a32_to_base64(encrypt_key(aes,nk)));
|
1454
|
+
}
|
1455
|
+
}
|
1456
|
+
}
|
1457
|
+
}
|
1458
|
+
return cr;
|
1459
|
+
}
|
1460
|
+
|
1461
|
+
// RSA-encrypt sharekey to newly RSA-equipped user
|
1462
|
+
// TODO: check source/ownership of sharekeys, prevent forged requests
|
1463
|
+
function crypto_procsr(sr)
|
1464
|
+
{
|
1465
|
+
var ctx = new Object;
|
1466
|
+
ctx.sr = sr;
|
1467
|
+
ctx.i = -2;
|
1468
|
+
|
1469
|
+
ctx.callback = function(res,ctx)
|
1470
|
+
{
|
1471
|
+
if (ctx.sr)
|
1472
|
+
{
|
1473
|
+
var pubkey;
|
1474
|
+
|
1475
|
+
if (typeof res == 'object' && typeof res[0] == 'object' && typeof res[0].pubk == 'string') u_pubkeys[ctx.sr[ctx.i+1]] = crypto_decodepubkey(res[0].pubk);
|
1476
|
+
|
1477
|
+
// collect all required pubkeys
|
1478
|
+
while (ctx.i < ctx.sr.length-4)
|
1479
|
+
{
|
1480
|
+
ctx.i += 2;
|
1481
|
+
|
1482
|
+
if (ctx.sr[ctx.i+1].length == 11 && !(pubkey = u_pubkeys[ctx.sr[ctx.i+1]]))
|
1483
|
+
{
|
1484
|
+
api_req([{ a : 'uk', u : ctx.sr[ctx.i+1] }],ctx);
|
1485
|
+
return;
|
1486
|
+
}
|
1487
|
+
}
|
1488
|
+
|
1489
|
+
var rsr = [];
|
1490
|
+
var sh;
|
1491
|
+
var n;
|
1492
|
+
|
1493
|
+
for (var i = 0; i < ctx.sr.length; i += 2)
|
1494
|
+
{
|
1495
|
+
sh = ctx.sr[i];
|
1496
|
+
|
1497
|
+
if (sh.length == 8)
|
1498
|
+
{
|
1499
|
+
// @@@ check auth
|
1500
|
+
if (u_sharekeys[sh])
|
1501
|
+
{
|
1502
|
+
if (d) console.log("Encrypting sharekey " + sh + " to user " + ctx.sr[i+1]);
|
1503
|
+
|
1504
|
+
if (pubkey = u_pubkeys[ctx.sr[i+1]])
|
1505
|
+
{
|
1506
|
+
// pubkey found: encrypt share key to it
|
1507
|
+
if (n = crypto_rsaencrypt(pubkey,a32_to_str(u_sharekeys[sh]))) rsr.push(sh,ctx.sr[i+1],base64urlencode(n));
|
1508
|
+
}
|
1509
|
+
}
|
1510
|
+
}
|
1511
|
+
|
1512
|
+
if (rsr.length) api_req([{ a : 'k', sr : rsr }]);
|
1513
|
+
}
|
1514
|
+
}
|
1515
|
+
}
|
1516
|
+
|
1517
|
+
ctx.callback(false,ctx);
|
1518
|
+
}
|
1519
|
+
|
1520
|
+
var keycache = new Object;
|
1521
|
+
|
1522
|
+
var rsa2aes = new Object;
|
1523
|
+
|
1524
|
+
// Try to decrypt ufs node.
|
1525
|
+
// Parameters: me - my user handle
|
1526
|
+
// master_aes - my master password's AES cipher
|
1527
|
+
// file - ufs node containing .k and .a
|
1528
|
+
// Output: .key and .name set if successful
|
1529
|
+
function crypto_processkey(me,master_aes,file)
|
1530
|
+
{
|
1531
|
+
var id, key, k, n;
|
1532
|
+
|
1533
|
+
if (!file.k)
|
1534
|
+
{
|
1535
|
+
if (!keycache[file.h]) return;
|
1536
|
+
|
1537
|
+
file.k = keycache[file.h];
|
1538
|
+
}
|
1539
|
+
|
1540
|
+
id = me;
|
1541
|
+
|
1542
|
+
// do I own the file? (user key is guaranteed to be first in .k)
|
1543
|
+
var p = file.k.indexOf(id + ':');
|
1544
|
+
|
1545
|
+
if (p)
|
1546
|
+
{
|
1547
|
+
// I don't - do I have a suitable sharekey?
|
1548
|
+
for (id in u_sharekeys)
|
1549
|
+
{
|
1550
|
+
p = file.k.indexOf(id + ':');
|
1551
|
+
|
1552
|
+
if (p >= 0 && (!p || file.k.charAt(p-1) == '/')) break;
|
1553
|
+
|
1554
|
+
p = -1;
|
1555
|
+
}
|
1556
|
+
}
|
1557
|
+
|
1558
|
+
if (p >= 0)
|
1559
|
+
{
|
1560
|
+
delete keycache[file.h];
|
1561
|
+
|
1562
|
+
var pp = file.k.indexOf('/',p);
|
1563
|
+
|
1564
|
+
if (pp < 0) pp = file.k.length;
|
1565
|
+
|
1566
|
+
p += id.length+1;
|
1567
|
+
|
1568
|
+
key = file.k.substr(p,pp-p);
|
1569
|
+
|
1570
|
+
// we have found a suitable key: decrypt!
|
1571
|
+
if (key.length < 46)
|
1572
|
+
{
|
1573
|
+
// short keys: AES
|
1574
|
+
k = base64_to_a32(key);
|
1575
|
+
|
1576
|
+
// check for permitted key lengths (4 == folder, 8 == file)
|
1577
|
+
if (k.length == 4 || k.length == 8)
|
1578
|
+
{
|
1579
|
+
// TODO: cache sharekeys in aes
|
1580
|
+
k = decrypt_key(id == me ? master_aes : new sjcl.cipher.aes(u_sharekeys[id]),k);
|
1581
|
+
}
|
1582
|
+
else
|
1583
|
+
{
|
1584
|
+
if (d) console.log("Received invalid key length (" + k.length + "): " + file.h);
|
1585
|
+
return;
|
1586
|
+
}
|
1587
|
+
}
|
1588
|
+
else
|
1589
|
+
{
|
1590
|
+
// long keys: RSA
|
1591
|
+
if (u_privk)
|
1592
|
+
{
|
1593
|
+
var t = mpi2b(base64urldecode(key));
|
1594
|
+
k = str_to_a32(b2s(RSAdecrypt(t,u_privk[2],u_privk[0],u_privk[1],u_privk[3])).substr(0,file.t ? 16 : 32));
|
1595
|
+
}
|
1596
|
+
else
|
1597
|
+
{
|
1598
|
+
if (d) console.log("Received RSA key, but have no public key published: " + file.h);
|
1599
|
+
return;
|
1600
|
+
}
|
1601
|
+
}
|
1602
|
+
|
1603
|
+
var ab = base64_to_ab(file.a);
|
1604
|
+
var o = dec_attr(ab,k);
|
1605
|
+
|
1606
|
+
if (typeof o == 'object')
|
1607
|
+
{
|
1608
|
+
if (typeof o.n == 'string')
|
1609
|
+
{
|
1610
|
+
if (file.h)
|
1611
|
+
{
|
1612
|
+
u_nodekeys[file.h] = k;
|
1613
|
+
if (key.length >= 46) rsa2aes[file.h] = a32_to_str(encrypt_key(u_k_aes,k));
|
1614
|
+
}
|
1615
|
+
|
1616
|
+
file.key = k;
|
1617
|
+
file.name = o.n;
|
1618
|
+
}
|
1619
|
+
}
|
1620
|
+
}
|
1621
|
+
else
|
1622
|
+
{
|
1623
|
+
if (d) console.log("Received no suitable key: " + file.h);
|
1624
|
+
|
1625
|
+
if (!missingkeys[file.h])
|
1626
|
+
{
|
1627
|
+
newmissingkeys = true;
|
1628
|
+
missingkeys[file.h] = true;
|
1629
|
+
}
|
1630
|
+
keycache[file.h] = file.k;
|
1631
|
+
}
|
1632
|
+
}
|
1633
|
+
|
1634
|
+
function crypto_sendrsa2aes()
|
1635
|
+
{
|
1636
|
+
var n;
|
1637
|
+
var nk = [];
|
1638
|
+
|
1639
|
+
for (n in rsa2aes) nk.push(n,base64urlencode(rsa2aes[n]));
|
1640
|
+
|
1641
|
+
if (nk.length) api_req([{ a : 'k', nk : nk }]);
|
1642
|
+
|
1643
|
+
rsa2aes = new Object;
|
1644
|
+
}
|
1645
|
+
|
1646
|
+
var missingkeys = new Object;
|
1647
|
+
var newmissingkeys = false;
|
1648
|
+
|
1649
|
+
function crypto_reqmissingkeys()
|
1650
|
+
{
|
1651
|
+
if (!newmissingkeys)
|
1652
|
+
{
|
1653
|
+
if (d) console.log('No new missing keys.');
|
1654
|
+
return;
|
1655
|
+
}
|
1656
|
+
|
1657
|
+
var i, j;
|
1658
|
+
var n, s, ni, si, sn;
|
1659
|
+
var cr = [[],[],[]];
|
1660
|
+
|
1661
|
+
ni = new Object;
|
1662
|
+
si = new Object;
|
1663
|
+
|
1664
|
+
for (n in missingkeys)
|
1665
|
+
{
|
1666
|
+
// TODO: optimization: don't request keys for own files
|
1667
|
+
sn = fm_getsharenodes(n);
|
1668
|
+
|
1669
|
+
for (j = sn.length; j--; )
|
1670
|
+
{
|
1671
|
+
s = sn[j];
|
1672
|
+
|
1673
|
+
if (typeof si[s] == 'undefined')
|
1674
|
+
{
|
1675
|
+
si[s] = cr[0].length;
|
1676
|
+
cr[0].push(s);
|
1677
|
+
}
|
1678
|
+
|
1679
|
+
if (typeof ni[n] == 'undefined')
|
1680
|
+
{
|
1681
|
+
ni[n] = cr[1].length;
|
1682
|
+
cr[1].push(n);
|
1683
|
+
}
|
1684
|
+
|
1685
|
+
cr[2].push(si[s],ni[n]);
|
1686
|
+
}
|
1687
|
+
}
|
1688
|
+
|
1689
|
+
if (!cr[1].length /*&& !missingsharekeys.length*/)
|
1690
|
+
{
|
1691
|
+
if (d) console.log('No missing keys');
|
1692
|
+
return;
|
1693
|
+
}
|
1694
|
+
|
1695
|
+
if (cr[0].length)
|
1696
|
+
{
|
1697
|
+
var ctx = new Object;
|
1698
|
+
|
1699
|
+
ctx.callback = function(res,ctx)
|
1700
|
+
{
|
1701
|
+
if (d) console.log("Processing crypto response");
|
1702
|
+
|
1703
|
+
if (typeof res == 'object' && typeof res[0] == 'object') crypto_proccr(res[0]);
|
1704
|
+
}
|
1705
|
+
|
1706
|
+
res = api_req([{ a : 'k', cr : cr }],ctx);
|
1707
|
+
}
|
1708
|
+
else if (d) console.log("Keys " + cr[1] + " missing, but no related shares found.");
|
1709
|
+
}
|
1710
|
+
|
1711
|
+
// process incoming cr, set fm keys and commit
|
1712
|
+
function crypto_proccr(cr)
|
1713
|
+
{
|
1714
|
+
var i;
|
1715
|
+
|
1716
|
+
// received keys in response, add
|
1717
|
+
for (i = 0; i < cr[2].length; i += 3) fm_updatekey(cr[1][cr[2][i+1]],cr[0][cr[2][i]] + ":" + cr[2][i+2]);
|
1718
|
+
|
1719
|
+
fm_commitkeyupdate();
|
1720
|
+
}
|
1721
|
+
|
1722
|
+
// process incoming missing key cr
|
1723
|
+
function crypto_procmcr(mcr)
|
1724
|
+
{
|
1725
|
+
var i;
|
1726
|
+
var si = new Object, ni = new Object;
|
1727
|
+
var sh, nh;
|
1728
|
+
var sc = new Object;
|
1729
|
+
var cr = [[],[],[]];
|
1730
|
+
|
1731
|
+
// received keys in response, add
|
1732
|
+
for (i = 0; i < mcr[2].length; i += 2)
|
1733
|
+
{
|
1734
|
+
sh = mcr[0][mcr[2][i]];
|
1735
|
+
|
1736
|
+
if (u_sharekeys[sh])
|
1737
|
+
{
|
1738
|
+
nh = mcr[1][mcr[2][i+1]];
|
1739
|
+
|
1740
|
+
if (u_nodekeys[nh])
|
1741
|
+
{
|
1742
|
+
if (typeof si[sh] == 'undefined')
|
1743
|
+
{
|
1744
|
+
sc[sh] = new sjcl.cipher.aes(u_sharekeys[sh]);
|
1745
|
+
si[sh] = cr[0].length;
|
1746
|
+
cr[0].push(sh);
|
1747
|
+
}
|
1748
|
+
|
1749
|
+
if (typeof ni[nh] == 'undefined')
|
1750
|
+
{
|
1751
|
+
ni[nh] = cr[1].length;
|
1752
|
+
cr[1].push(nh);
|
1753
|
+
}
|
1754
|
+
|
1755
|
+
cr[2].push(si[sh],ni[nh],a32_to_base64(encrypt_key(sc[sh],u_nodekeys[nh])));
|
1756
|
+
}
|
1757
|
+
}
|
1758
|
+
}
|
1759
|
+
|
1760
|
+
if (cr[0].length) api_req([{ a : 'k', cr : cr }]);
|
1761
|
+
}
|
1762
|
+
|
1763
|
+
var rsasharekeys = new Object;
|
1764
|
+
|
1765
|
+
function crypto_process_sharekey(handle,key)
|
1766
|
+
{
|
1767
|
+
if (key.length > 22)
|
1768
|
+
{
|
1769
|
+
key = mpi2b(base64urldecode(key));
|
1770
|
+
var k = str_to_a32(b2s(RSAdecrypt(key,u_privk[2],u_privk[0],u_privk[1],u_privk[3])).substr(0,16));
|
1771
|
+
rsasharekeys[handle] = true;
|
1772
|
+
return k;
|
1773
|
+
}
|
1774
|
+
else return decrypt_key(u_k_aes,base64_to_a32(key));
|
1775
|
+
}
|
1776
|
+
|
1777
|
+
function crypto_share_rsa2aes()
|
1778
|
+
{
|
1779
|
+
var rsr = [];
|
1780
|
+
|
1781
|
+
for (n in rsasharekeys)
|
1782
|
+
{
|
1783
|
+
if (u_sharekeys[n])
|
1784
|
+
{
|
1785
|
+
// pubkey found: encrypt share key to it
|
1786
|
+
rsr.push(n,u_handle,a32_to_base64(encrypt_key(u_k_aes,u_sharekeys[n])));
|
1787
|
+
}
|
1788
|
+
}
|
1789
|
+
|
1790
|
+
if (rsr.length)
|
1791
|
+
{
|
1792
|
+
api_req([{ a : 'k', sr : rsr }]);
|
1793
|
+
rsasharekeys = new Object;
|
1794
|
+
}
|
1795
|
+
}
|