cryptpad/www/file/file-crypto.js

217 lines
6.1 KiB
JavaScript
Raw Normal View History

define([
'/bower_components/tweetnacl/nacl-fast.min.js',
], function () {
var Nacl = window.nacl;
2017-04-28 09:45:53 +00:00
var PARANOIA = true;
2017-04-28 09:45:53 +00:00
var plainChunkLength = 128 * 1024;
var cypherChunkLength = 131088;
2017-05-10 16:57:25 +00:00
var computeEncryptedSize = function (bytes, meta) {
2017-05-12 10:12:51 +00:00
var metasize = Nacl.util.decodeUTF8(JSON.stringify(meta)).length;
2017-05-10 16:57:25 +00:00
var chunks = Math.ceil(bytes / plainChunkLength);
2017-05-12 10:12:51 +00:00
return metasize + 18 + (chunks * 16) + bytes;
2017-05-10 16:57:25 +00:00
};
2017-05-09 16:54:56 +00:00
var encodePrefix = function (p) {
return [
65280, // 255 << 8
255,
].map(function (n, i) {
return (p & n) >> ((1 - i) * 8);
});
};
var decodePrefix = function (A) {
return (A[0] << 8) | A[1];
};
var slice = function (A) {
return Array.prototype.slice.call(A);
};
2017-04-28 09:45:53 +00:00
var createNonce = function () {
return new Uint8Array(new Array(24).fill(0));
};
var increment = function (N) {
var l = N.length;
while (l-- > 1) {
2017-04-28 09:45:53 +00:00
if (PARANOIA) {
if (typeof(N[l]) !== 'number') {
throw new Error('E_UNSAFE_TYPE');
}
if (N[l] > 255) {
throw new Error('E_OUT_OF_BOUNDS');
}
}
/* jshint probably suspects this is unsafe because we lack types
but as long as this is only used on nonces, it should be safe */
if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
N[l] = 0;
2017-04-28 09:45:53 +00:00
// you don't need to worry about this running out.
// you'd need a REAAAALLY big file
if (l === 0) {
throw new Error('E_NONCE_TOO_LARGE');
}
}
};
2017-04-28 09:45:53 +00:00
var joinChunks = function (chunks) {
2017-05-22 10:04:47 +00:00
return new Blob(chunks);
};
2017-05-22 10:31:00 +00:00
var decryptMetadata = function (u8, key) {
var prefix = u8.subarray(0, 2);
var metadataLength = decodePrefix(prefix);
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
var metaChunk = Nacl.secretbox.open(metaBox, createNonce(), key);
try {
return JSON.parse(Nacl.util.encodeUTF8(metaChunk));
}
catch (e) { return null; }
};
2017-05-22 10:04:47 +00:00
var decrypt = function (u8, key, done, progress) {
var MAX = u8.length;
var _progress = function (offset) {
if (typeof(progress) !== 'function') { return; }
progress(Math.min(1, offset / MAX));
2017-05-10 15:13:14 +00:00
};
2017-04-28 09:45:53 +00:00
var nonce = createNonce();
var i = 0;
2017-04-28 09:45:53 +00:00
2017-05-10 15:13:14 +00:00
var prefix = u8.subarray(0, 2);
var metadataLength = decodePrefix(prefix);
var res = {
metadata: undefined,
};
2017-05-10 15:13:14 +00:00
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
var metaChunk = Nacl.secretbox.open(metaBox, nonce, key);
increment(nonce);
try {
res.metadata = JSON.parse(Nacl.util.encodeUTF8(metaChunk));
} catch (e) {
2017-05-22 10:04:47 +00:00
return window.setTimeout(function () {
done('E_METADATA_DECRYPTION');
});
}
if (!res.metadata) {
return void setTimeout(function () {
2017-05-22 10:04:47 +00:00
done('NO_METADATA');
});
}
2017-05-22 10:04:47 +00:00
var takeChunk = function (cb) {
2017-05-10 15:13:14 +00:00
var start = i * cypherChunkLength + 2 + metadataLength;
var end = start + cypherChunkLength;
i++;
var box = new Uint8Array(u8.subarray(start, end));
// decrypt the chunk
var plaintext = Nacl.secretbox.open(box, nonce, key);
increment(nonce);
2017-05-22 10:04:47 +00:00
if (!plaintext) { return cb('DECRYPTION_ERROR'); }
_progress(end);
cb(void 0, plaintext);
2017-04-28 09:45:53 +00:00
};
var chunks = [];
2017-05-22 10:04:47 +00:00
var again = function () {
takeChunk(function (e, plaintext) {
if (e) {
return setTimeout(function () {
done(e);
});
}
if (plaintext) {
if (i * cypherChunkLength < u8.length) { // not done
chunks.push(plaintext);
return setTimeout(again);
}
chunks.push(plaintext);
res.content = joinChunks(chunks);
return done(void 0, res);
}
done('UNEXPECTED_ENDING');
});
};
2017-05-22 10:04:47 +00:00
again();
};
2017-04-28 09:45:53 +00:00
// metadata
/* { filename: 'raccoon.jpg', type: 'image/jpeg' } */
var encrypt = function (u8, metadata, key) {
2017-04-28 09:45:53 +00:00
var nonce = createNonce();
// encode metadata
var metaBuffer = Array.prototype.slice
.call(Nacl.util.decodeUTF8(JSON.stringify(metadata)));
2017-05-10 15:13:14 +00:00
var plaintext = new Uint8Array(metaBuffer);
2017-04-28 09:45:53 +00:00
var i = 0;
2017-04-28 09:45:53 +00:00
var state = 0;
var next = function (cb) {
2017-05-12 10:12:51 +00:00
if (state === 2) { return void cb(); }
var start;
var end;
var part;
var box;
2017-04-28 09:45:53 +00:00
2017-05-10 15:13:14 +00:00
if (state === 0) { // metadata...
part = new Uint8Array(plaintext);
box = Nacl.secretbox(part, nonce, key);
increment(nonce);
2017-05-10 15:13:14 +00:00
if (box.length > 65535) {
return void cb('METADATA_TOO_LARGE');
}
2017-05-10 15:13:14 +00:00
var prefixed = new Uint8Array(encodePrefix(box.length)
.concat(slice(box)));
state++;
2017-05-10 16:57:25 +00:00
2017-05-10 15:13:14 +00:00
return void cb(void 0, prefixed);
}
// encrypt the rest of the file...
2017-04-28 09:45:53 +00:00
start = i * plainChunkLength;
end = start + plainChunkLength;
part = u8.subarray(start, end);
2017-04-28 09:45:53 +00:00
box = Nacl.secretbox(part, nonce, key);
increment(nonce);
i++;
2017-04-28 09:45:53 +00:00
// regular data is done
if (i * plainChunkLength >= u8.length) { state = 2; }
2017-05-10 15:13:14 +00:00
return void cb(void 0, box);
};
2017-04-28 09:45:53 +00:00
return next;
2017-04-28 09:45:53 +00:00
};
return {
decrypt: decrypt,
2017-04-28 09:45:53 +00:00
encrypt: encrypt,
joinChunks: joinChunks,
2017-05-10 16:57:25 +00:00
computeEncryptedSize: computeEncryptedSize,
2017-05-22 10:31:00 +00:00
decryptMetadata: decryptMetadata,
};
});