const FileSender = window.FileSender; const FileReceiver = window.FileReceiver; const FakeFile = window.FakeFile; const assert = window.assert; const server = window.server; const hexToArray = window.hexToArray; const arrayToHex = window.arrayToHex; const sinon = window.sinon; let file; let encryptedIV; let fileHash; let secretKey; let originalBlob; describe('File Sender', function() { before(function() { server.respondImmediately = true; server.respondWith( 'POST', '/upload', function(request) { const reader = new FileReader(); reader.readAsArrayBuffer(request.requestBody.get('data')); reader.onload = function(event) { file = this.result; } const responseObj = JSON.parse(request.requestHeaders['X-File-Metadata']); request.respond( 200, {'Content-Type': 'application/json'}, JSON.stringify({url: 'some url', id: responseObj.id, delete: responseObj.delete}) ) } ) }) it('Should get a loading event emission', function() { const file = new FakeFile('hello_world.txt', ['This is some data.']) const fs = new FileSender(file); let testLoading = true; fs.on('loading', isStillLoading => { assert(!(!testLoading && isStillLoading)); testLoading = isStillLoading; }) return fs.upload() .then(info => { assert(info); assert(!testLoading); }) .catch(err => { console.log(err, err.stack); assert.fail(); }); }) it('Should get a hashing event emission', function() { const file = new FakeFile('hello_world.txt', ['This is some data.']) const fs = new FileSender(file); let testHashing = true; fs.on('hashing', isStillHashing => { assert(!(!testHashing && isStillHashing)); testHashing = isStillHashing; }) return fs.upload() .then(info => { assert(info); assert(!testHashing); }) .catch(err => { console.log(err, err.stack); assert.fail(); }); }) it('Should get a encrypting event emission', function() { const file = new FakeFile('hello_world.txt', ['This is some data.']) const fs = new FileSender(file); let testEncrypting = true; fs.on('encrypting', isStillEncrypting => { assert(!(!testEncrypting && isStillEncrypting)); testEncrypting = isStillEncrypting; }) return fs.upload() .then(info => { assert(info); assert(!testEncrypting); }) .catch(err => { console.log(err, err.stack); assert.fail(); }); }) it('Should encrypt a file properly', function(done) { const newFile = new FakeFile('hello_world.txt', ['This is some data.']) const fs = new FileSender(newFile); fs.upload().then(info => { const key = info.secretKey; secretKey = info.secretKey; const IV = info.fileId; encryptedIV = info.fileId; const readRaw = new FileReader; readRaw.onload = function(event) { const rawArray = new Uint8Array(this.result); originalBlob = rawArray; window.crypto.subtle.digest('SHA-256', rawArray).then(hash => { fileHash = hash; window.crypto.subtle.importKey( 'jwk', { kty: 'oct', k: key, alg: 'A128GCM', ext: true, }, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt'] ) .then(cryptoKey => { window.crypto.subtle.encrypt( { name: 'AES-GCM', iv: hexToArray(IV), additionalData: hash, tagLength: 128 }, cryptoKey, rawArray ) .then(encrypted => { assert(new Uint8Array(encrypted).toString() === new Uint8Array(file).toString()); done(); }) }) }) } readRaw.readAsArrayBuffer(newFile); }) }) }); describe('File Receiver', function() { class FakeXHR { constructor() { this.response = file; this.status = 200; } static setup() { FakeXHR.prototype.open = sinon.spy(); FakeXHR.prototype.send = function () { this.onload(); } FakeXHR.prototype.originalXHR = window.XMLHttpRequest; FakeXHR.prototype.getResponseHeader = function () { return JSON.stringify({ aad: arrayToHex(new Uint8Array(fileHash)), filename: 'hello_world.txt', id: encryptedIV }) } window.XMLHttpRequest = FakeXHR; } static restore() { // originalXHR is a sinon FakeXMLHttpRequest, since // fakeServer.create() is called in frontend.bundle.js window.XMLHttpRequest.prototype.originalXHR.restore(); } } const cb = function(done) { if (file === undefined || encryptedIV === undefined || fileHash === undefined || secretKey === undefined) { assert.fail('Please run file sending tests before trying to receive the files.'); done(); } FakeXHR.setup(); done(); } before(cb) after(function() { FakeXHR.restore(); }) it('Should decrypt properly', function() { const fr = new FileReceiver(); location.hash = secretKey; return fr.download().then(([decrypted, name]) => { assert(name); assert(new Uint8Array(decrypted).toString() === new Uint8Array(originalBlob).toString()) }).catch(err => { console.log(err, err.stack); assert.fail(); }) }) it('Should emit decrypting events', function() { const fr = new FileReceiver(); location.hash = secretKey; let testDecrypting = true; fr.on('decrypting', isStillDecrypting => { assert(!(!testDecrypting && isStillDecrypting)); testDecrypting = isStillDecrypting; }); fr.on('safe', isSafe => { assert(isSafe); }) return fr.download().then(([decrypted, name]) => { assert(decrypted); assert(name); assert(!testDecrypting); }).catch(err => { console.log(err, err.stack); assert.fail(); }) }) it('Should emit hashing events', function() { const fr = new FileReceiver(); location.hash = secretKey; let testHashing = true; fr.on('hashing', isStillHashing => { assert(!(!testHashing && isStillHashing)); testHashing = isStillHashing; }); fr.on('safe', isSafe => { assert(isSafe); }) return fr.download().then(([decrypted, name]) => { assert(decrypted); assert(name); assert(!testHashing); }).catch(err => { assert.fail(); }) }) it('Should catch fraudulent checksums', function(done) { // Use the secret key and file hash of the previous file to encrypt, // which has a different hash than this one (different strings). const newFile = new FakeFile('hello_world.txt', ['This is some data, with a changed hash.']) const readRaw = new FileReader(); readRaw.onload = function(event) { const plaintext = new Uint8Array(this.result); window.crypto.subtle.importKey( 'jwk', { kty: 'oct', k: secretKey, alg: 'A128GCM', ext: true }, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt'] ) .then(key => { // The file hash used here is the hash of the fake // file from the previous test; it's a phony checksum. return window.crypto.subtle.encrypt( { name: 'AES-GCM', iv: hexToArray(encryptedIV), additionalData: fileHash, tagLength: 128 }, key, plaintext ) }) .then(encrypted => { file = encrypted; const fr = new FileReceiver(); location.hash = secretKey; fr.on('unsafe', isUnsafe => { assert(isUnsafe) }) fr.on('safe', () => { // This event should not be emitted. assert.fail(); }) fr.download().then(() => { assert.fail(); done(); }).catch(err => { assert(1); done(); }) }) } readRaw.readAsArrayBuffer(newFile); }) it('Should not decrypt with an incorrect checksum', function() { FakeXHR.prototype.getResponseHeader = function () { return JSON.stringify({ aad: 'some_bad_hashz', filename: 'hello_world.txt', id: encryptedIV }) } const fr = new FileReceiver(); location.hash = secretKey; return fr.download().then(([decrypted, name]) => { assert(decrypted); assert(name); assert.fail(); }).catch(err => { assert(1); }) }) })