Skip to content
This repository has been archived by the owner on May 22, 2021. It is now read-only.

Gcm #106

Merged
merged 12 commits into from
Jul 10, 2017
55 changes: 25 additions & 30 deletions frontend/src/fileReceiver.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
const EventEmitter = require('events');
const { strToIv } = require('./utils');

const Raven = window.Raven;
const { hexToArray } = require('./utils');

class FileReceiver extends EventEmitter {
constructor() {
super();
this.salt = strToIv(location.pathname.slice(10, -1));
this.salt = hexToArray(location.pathname.slice(10, -1));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iv is coming from X-File-Metadata now so we shouldn't need this anymore.

}

download() {
Expand Down Expand Up @@ -34,11 +32,12 @@ class FileReceiver extends EventEmitter {
const blob = new Blob([this.response]);
const fileReader = new FileReader();
fileReader.onload = function() {
const meta = JSON.parse(xhr.getResponseHeader('X-File-Metadata'));
resolve({
data: this.result,
fname: xhr
.getResponseHeader('Content-Disposition')
.match(/=(.+)/)[1]
aad: meta.aad,
filename: meta.filename,
iv: meta.iv
});
};

Expand All @@ -54,36 +53,32 @@ class FileReceiver extends EventEmitter {
{
kty: 'oct',
k: location.hash.slice(1),
alg: 'A128CBC',
alg: 'A128GCM',
ext: true
},
{
name: 'AES-CBC'
name: 'AES-GCM'
},
true,
['encrypt', 'decrypt']
)
])
.then(([fdata, key]) => {
const salt = this.salt;
return Promise.all([
window.crypto.subtle.decrypt(
{
name: 'AES-CBC',
iv: salt
},
key,
fdata.data
),
new Promise((resolve, reject) => {
resolve(fdata.fname);
})
]);
})
.catch(err => {
Raven.captureException(err);
return Promise.reject(err);
});
]).then(([fdata, key]) => {
return Promise.all([
window.crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: hexToArray(fdata.iv),
additionalData: hexToArray(fdata.aad),
tagLength: 128
},
key,
fdata.data
),
new Promise((resolve, reject) => {
resolve(fdata.filename);
})
]);
});
}
}

Expand Down
56 changes: 36 additions & 20 deletions frontend/src/fileSender.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
const EventEmitter = require('events');
const { ivToStr } = require('./utils');
const { arrayToHex } = require('./utils');

const Raven = window.Raven;

class FileSender extends EventEmitter {
constructor(file) {
super();
this.file = file;
this.iv = window.crypto.getRandomValues(new Uint8Array(16));
this.iv = window.crypto.getRandomValues(new Uint8Array(12));
this.aad = window.crypto.getRandomValues(new Uint8Array(6));
}

static delete(fileId, token) {
Expand Down Expand Up @@ -37,14 +38,18 @@ class FileSender extends EventEmitter {

upload() {
return Promise.all([
window.crypto.subtle.generateKey(
{
name: 'AES-CBC',
length: 128
},
true,
['encrypt', 'decrypt']
),
window.crypto.subtle
.generateKey(
{
name: 'AES-GCM',
length: 128
},
true,
['encrypt', 'decrypt']
)
.catch(err =>
console.log('There was an error generating a crypto key')
),
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsArrayBuffer(this.file);
Expand All @@ -55,25 +60,28 @@ class FileSender extends EventEmitter {
])
.then(([secretKey, plaintext]) => {
return Promise.all([
window.crypto.subtle.encrypt(
{
name: 'AES-CBC',
iv: this.iv
},
secretKey,
plaintext
),
window.crypto.subtle
.encrypt(
{
name: 'AES-GCM',
iv: this.iv,
additionalData: this.aad,
tagLength: 128
},
secretKey,
plaintext
)
.catch(err => console.log('Error with encrypting.')),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to rethrow

window.crypto.subtle.exportKey('jwk', secretKey)
]);
})
.then(([encrypted, keydata]) => {
return new Promise((resolve, reject) => {
const file = this.file;
const fileId = ivToStr(this.iv);
const fileId = arrayToHex(this.iv);
const dataView = new DataView(encrypted);
const blob = new Blob([dataView], { type: file.type });
const fd = new FormData();
fd.append('fname', file.name);
fd.append('data', blob, file.name);

const xhr = new XMLHttpRequest();
Expand All @@ -99,6 +107,14 @@ class FileSender extends EventEmitter {
};

xhr.open('post', '/upload/' + fileId, true);
xhr.setRequestHeader(
'X-File-Metadata',
JSON.stringify({
aad: arrayToHex(this.aad),
iv: fileId,
filename: file.name
})
);
xhr.send(fd);
});
})
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function ivToStr(iv) {
function arrayToHex(iv) {
let hexStr = '';
for (const i in iv) {
if (iv[i] < 16) {
Expand All @@ -11,8 +11,8 @@ function ivToStr(iv) {
return hexStr;
}

function strToIv(str) {
const iv = new Uint8Array(16);
function hexToArray(str) {
const iv = new Uint8Array(str.length / 2);
for (let i = 0; i < str.length; i += 2) {
iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16);
}
Expand All @@ -33,7 +33,7 @@ function notify(str) {
}

module.exports = {
ivToStr,
strToIv,
arrayToHex,
hexToArray,
notify
};
2 changes: 1 addition & 1 deletion server/log.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const conf = require('./config.js');

const isProduction = conf.env === 'production'
const isProduction = conf.env === 'production';

const mozlog = require('mozlog')({
app: 'FirefoxFileshare',
Expand Down
33 changes: 21 additions & 12 deletions server/portal_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,14 @@ app.get('/assets/download/:id', (req, res) => {
}

storage
.filename(id)
.then(reply => {
.metadata(id)
.then(meta => {
storage.length(id).then(contentLength => {
res.writeHead(200, {
'Content-Disposition': 'attachment; filename=' + reply,
'Content-Disposition': 'attachment; filename=' + meta.filename,
'Content-Type': 'application/octet-stream',
'Content-Length': contentLength
'Content-Length': contentLength,
'X-File-Metadata': JSON.stringify(meta)
});
const file_stream = storage.get(id);

Expand Down Expand Up @@ -136,20 +137,24 @@ app.post('/delete/:id', (req, res) => {
});

app.post('/upload/:id', (req, res, next) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer need the :id parameter in the upload url. new_id should be created at this level instead of in storage.js. It'd also be better to create meta.delete up here too. Ideally storage.js should only be concerned with saving and fetching the data from persistent storage and leaving the logic of what the data is and how it's created to something else (this function).

if (!validateID(req.params.id)) {
if (!validateIV(req.params.id)) {
res.sendStatus(404);
return;
}

const meta = JSON.parse(req.header('X-File-Metadata'));
log.info('meta', meta);
req.pipe(req.busboy);

req.busboy.on('file', (fieldname, file, filename) => {
log.info('Uploading:', req.params.id);

const protocol = conf.env === 'production' ? 'https' : req.protocol;
const url = `${protocol}://${req.get('host')}/download/${req.params.id}/`;

storage.set(req.params.id, file, filename, url).then(linkAndID => {
res.json(linkAndID);
storage.set(req.params.id, file, filename, meta).then(([delete_token, new_id]) => {
const protocol = conf.env === 'production' ? 'https' : req.protocol;
const url = `${protocol}://${req.get('host')}/download/${new_id}/`;
res.json({
url,
delete: delete_token
});
});
});
});
Expand All @@ -171,5 +176,9 @@ app.listen(conf.listen_port, () => {
});

const validateID = route_id => {
return route_id.match(/^[0-9a-fA-F]{32}$/) !== null;
return route_id.match(/^[0-9a-fA-F]{10}$/) !== null;
};

const validateIV = route_id => {
return route_id.match(/^[0-9a-fA-F]{24}$/) !== null;
};
Loading