commit
52173bf6e7
|
@ -9,7 +9,8 @@ $(document).ready(function() {
|
||||||
$('#send-file').click(() => {
|
$('#send-file').click(() => {
|
||||||
window.location.replace(`${window.location.origin}`);
|
window.location.replace(`${window.location.origin}`);
|
||||||
});
|
});
|
||||||
const download = () => {
|
$('#download-btn').click(download);
|
||||||
|
function download() {
|
||||||
const fileReceiver = new FileReceiver();
|
const fileReceiver = new FileReceiver();
|
||||||
const name = document.createElement('p');
|
const name = document.createElement('p');
|
||||||
const $btn = $('#download-btn');
|
const $btn = $('#download-btn');
|
||||||
|
@ -37,20 +38,20 @@ $(document).ready(function() {
|
||||||
fileReceiver.on('decrypting', isStillDecrypting => {
|
fileReceiver.on('decrypting', isStillDecrypting => {
|
||||||
// The file is being decrypted
|
// The file is being decrypted
|
||||||
if (isStillDecrypting) {
|
if (isStillDecrypting) {
|
||||||
console.log('Decrypting')
|
console.log('Decrypting');
|
||||||
} else {
|
} else {
|
||||||
console.log('Done decrypting')
|
console.log('Done decrypting');
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
fileReceiver.on('hashing', isStillHashing => {
|
fileReceiver.on('hashing', isStillHashing => {
|
||||||
// The file is being hashed to make sure a malicious user hasn't tampered with it
|
// The file is being hashed to make sure a malicious user hasn't tampered with it
|
||||||
if (isStillHashing) {
|
if (isStillHashing) {
|
||||||
console.log('Checking file integrity')
|
console.log('Checking file integrity');
|
||||||
} else {
|
} else {
|
||||||
console.log('Integrity check done')
|
console.log('Integrity check done');
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
fileReceiver
|
fileReceiver
|
||||||
.download()
|
.download()
|
||||||
|
@ -84,7 +85,5 @@ $(document).ready(function() {
|
||||||
Raven.captureException(err);
|
Raven.captureException(err);
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
window.download = download;
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -61,52 +61,60 @@ class FileReceiver extends EventEmitter {
|
||||||
true,
|
true,
|
||||||
['encrypt', 'decrypt']
|
['encrypt', 'decrypt']
|
||||||
)
|
)
|
||||||
]).then(([fdata, key]) => {
|
])
|
||||||
this.emit('decrypting', true);
|
.then(([fdata, key]) => {
|
||||||
return Promise.all([
|
this.emit('decrypting', true);
|
||||||
window.crypto.subtle.decrypt(
|
|
||||||
{
|
|
||||||
name: 'AES-GCM',
|
|
||||||
iv: hexToArray(fdata.iv),
|
|
||||||
additionalData: hexToArray(fdata.aad)
|
|
||||||
},
|
|
||||||
key,
|
|
||||||
fdata.data
|
|
||||||
).then(decrypted => {
|
|
||||||
this.emit('decrypting', false);
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
resolve(decrypted);
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
resolve(fdata.filename);
|
|
||||||
}),
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
resolve(hexToArray(fdata.aad));
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
}).then(([decrypted, fname, proposedHash]) => {
|
|
||||||
this.emit('hashing', true);
|
|
||||||
return window.crypto.subtle.digest('SHA-256', decrypted).then(calculatedHash => {
|
|
||||||
this.emit('hashing', false);
|
|
||||||
const integrity = new Uint8Array(calculatedHash).toString() === proposedHash.toString();
|
|
||||||
if (!integrity) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
console.log('This file has been tampered with.')
|
|
||||||
reject();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
|
window.crypto.subtle
|
||||||
|
.decrypt(
|
||||||
|
{
|
||||||
|
name: 'AES-GCM',
|
||||||
|
iv: hexToArray(fdata.iv),
|
||||||
|
additionalData: hexToArray(fdata.aad)
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
fdata.data
|
||||||
|
)
|
||||||
|
.then(decrypted => {
|
||||||
|
this.emit('decrypting', false);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
resolve(decrypted);
|
||||||
|
});
|
||||||
|
}),
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
resolve(decrypted);
|
resolve(fdata.filename);
|
||||||
}),
|
}),
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
resolve(fname);
|
resolve(hexToArray(fdata.aad));
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
})
|
.then(([decrypted, fname, proposedHash]) => {
|
||||||
|
this.emit('hashing', true);
|
||||||
|
return window.crypto.subtle
|
||||||
|
.digest('SHA-256', decrypted)
|
||||||
|
.then(calculatedHash => {
|
||||||
|
this.emit('hashing', false);
|
||||||
|
const integrity =
|
||||||
|
new Uint8Array(calculatedHash).toString() ===
|
||||||
|
proposedHash.toString();
|
||||||
|
if (!integrity) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log('This file has been tampered with.');
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
resolve(decrypted);
|
||||||
|
}),
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
resolve(fname);
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,8 +61,8 @@ class FileSender extends EventEmitter {
|
||||||
window.crypto.subtle.digest('SHA-256', plaintext).then(hash => {
|
window.crypto.subtle.digest('SHA-256', plaintext).then(hash => {
|
||||||
self.emit('hashing', false);
|
self.emit('hashing', false);
|
||||||
self.emit('encrypting', true);
|
self.emit('encrypting', true);
|
||||||
resolve({plaintext: plaintext, hash: new Uint8Array(hash)});
|
resolve({ plaintext: plaintext, hash: new Uint8Array(hash) });
|
||||||
})
|
});
|
||||||
};
|
};
|
||||||
reader.onerror = function(err) {
|
reader.onerror = function(err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -81,14 +81,17 @@ class FileSender extends EventEmitter {
|
||||||
},
|
},
|
||||||
secretKey,
|
secretKey,
|
||||||
file.plaintext
|
file.plaintext
|
||||||
).then(encrypted => {
|
)
|
||||||
|
.then(encrypted => {
|
||||||
self.emit('encrypting', false);
|
self.emit('encrypting', false);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
resolve(encrypted);
|
resolve(encrypted);
|
||||||
})
|
});
|
||||||
}),
|
}),
|
||||||
window.crypto.subtle.exportKey('jwk', secretKey),
|
window.crypto.subtle.exportKey('jwk', secretKey),
|
||||||
new Promise((resolve, reject) => { resolve(file.hash) })
|
new Promise((resolve, reject) => {
|
||||||
|
resolve(file.hash);
|
||||||
|
})
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
.then(([encrypted, keydata, hash]) => {
|
.then(([encrypted, keydata, hash]) => {
|
||||||
|
|
|
@ -8,8 +8,10 @@ $(document).ready(function() {
|
||||||
gcmCompliant().catch(err => {
|
gcmCompliant().catch(err => {
|
||||||
$('#page-one').hide();
|
$('#page-one').hide();
|
||||||
$('#compliance-error').show();
|
$('#compliance-error').show();
|
||||||
})
|
});
|
||||||
|
|
||||||
|
$('#file-upload').change(onUpload);
|
||||||
|
$('#page-one').on('dragover', allowDrop).on('drop', onUpload);
|
||||||
// reset copy button
|
// reset copy button
|
||||||
const $copyBtn = $('#copy-btn');
|
const $copyBtn = $('#copy-btn');
|
||||||
$copyBtn.attr('disabled', false);
|
$copyBtn.attr('disabled', false);
|
||||||
|
@ -61,11 +63,11 @@ $(document).ready(function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// on file upload by browse or drag & drop
|
// on file upload by browse or drag & drop
|
||||||
window.onUpload = event => {
|
function onUpload(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let file = '';
|
let file = '';
|
||||||
if (event.type === 'drop') {
|
if (event.type === 'drop') {
|
||||||
file = event.dataTransfer.files[0];
|
file = event.originalEvent.dataTransfer.files[0];
|
||||||
} else {
|
} else {
|
||||||
file = event.target.files[0];
|
file = event.target.files[0];
|
||||||
}
|
}
|
||||||
|
@ -88,29 +90,29 @@ $(document).ready(function() {
|
||||||
fileSender.on('loading', isStillLoading => {
|
fileSender.on('loading', isStillLoading => {
|
||||||
// The file is loading into Firefox at this stage
|
// The file is loading into Firefox at this stage
|
||||||
if (isStillLoading) {
|
if (isStillLoading) {
|
||||||
console.log('Processing')
|
console.log('Processing');
|
||||||
} else {
|
} else {
|
||||||
console.log('Finished processing')
|
console.log('Finished processing');
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
fileSender.on('hashing', isStillHashing => {
|
fileSender.on('hashing', isStillHashing => {
|
||||||
// The file is being hashed
|
// The file is being hashed
|
||||||
if (isStillHashing) {
|
if (isStillHashing) {
|
||||||
console.log('Hashing');
|
console.log('Hashing');
|
||||||
} else {
|
} else {
|
||||||
console.log('Finished hashing')
|
console.log('Finished hashing');
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
fileSender.on('encrypting', isStillEncrypting => {
|
fileSender.on('encrypting', isStillEncrypting => {
|
||||||
// The file is being encrypted
|
// The file is being encrypted
|
||||||
if (isStillEncrypting) {
|
if (isStillEncrypting) {
|
||||||
console.log('Encrypting');
|
console.log('Encrypting');
|
||||||
} else {
|
} else {
|
||||||
console.log('Finished encrypting')
|
console.log('Finished encrypting');
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
fileSender
|
fileSender
|
||||||
.upload()
|
.upload()
|
||||||
|
@ -143,11 +145,11 @@ $(document).ready(function() {
|
||||||
$('#page-one').hide();
|
$('#page-one').hide();
|
||||||
$('#upload-error').show();
|
$('#upload-error').show();
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
window.allowDrop = function(ev) {
|
function allowDrop(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
};
|
}
|
||||||
|
|
||||||
function checkExistence(id, populate) {
|
function checkExistence(id, populate) {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
|
@ -34,34 +34,38 @@ function notify(str) {
|
||||||
|
|
||||||
function gcmCompliant() {
|
function gcmCompliant() {
|
||||||
try {
|
try {
|
||||||
return window.crypto.subtle.generateKey(
|
return window.crypto.subtle
|
||||||
{
|
.generateKey(
|
||||||
name: 'AES-GCM',
|
|
||||||
length: 128
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
['encrypt', 'decrypt']
|
|
||||||
).then(key => {
|
|
||||||
return window.crypto.subtle.encrypt(
|
|
||||||
{
|
{
|
||||||
name: 'AES-GCM',
|
name: 'AES-GCM',
|
||||||
iv: window.crypto.getRandomValues(new Uint8Array(12)),
|
length: 128
|
||||||
additionalData: window.crypto.getRandomValues(new Uint8Array(6)),
|
|
||||||
tagLength: 128
|
|
||||||
},
|
},
|
||||||
key,
|
true,
|
||||||
new ArrayBuffer(8)
|
['encrypt', 'decrypt']
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(key => {
|
||||||
return Promise.resolve()
|
return window.crypto.subtle
|
||||||
|
.encrypt(
|
||||||
|
{
|
||||||
|
name: 'AES-GCM',
|
||||||
|
iv: window.crypto.getRandomValues(new Uint8Array(12)),
|
||||||
|
additionalData: window.crypto.getRandomValues(new Uint8Array(6)),
|
||||||
|
tagLength: 128
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
new ArrayBuffer(8)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
return Promise.reject();
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
return Promise.reject()
|
return Promise.reject();
|
||||||
})
|
});
|
||||||
}).catch(err => {
|
} catch (err) {
|
||||||
return Promise.reject();
|
|
||||||
})
|
|
||||||
} catch(err) {
|
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,30 @@ app.engine(
|
||||||
app.set('view engine', 'handlebars');
|
app.set('view engine', 'handlebars');
|
||||||
|
|
||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
|
app.use(
|
||||||
|
helmet.contentSecurityPolicy({
|
||||||
|
directives: {
|
||||||
|
defaultSrc: ['\'self\''],
|
||||||
|
connectSrc: [
|
||||||
|
'\'self\'',
|
||||||
|
'https://sentry.prod.mozaws.net',
|
||||||
|
'https://www.google-analytics.com',
|
||||||
|
'https://ssl.google-analytics.com'
|
||||||
|
],
|
||||||
|
imgSrc: [
|
||||||
|
'\'self\'',
|
||||||
|
'https://www.google-analytics.com',
|
||||||
|
'https://ssl.google-analytics.com'
|
||||||
|
],
|
||||||
|
scriptSrc: ['\'self\'', 'https://ssl.google-analytics.com'],
|
||||||
|
styleSrc: ['\'self\'', 'https://code.cdn.mozilla.net'],
|
||||||
|
fontSrc: ['\'self\'', 'https://code.cdn.mozilla.net'],
|
||||||
|
formAction: ['\'none\''],
|
||||||
|
frameAncestors: ['\'none\''],
|
||||||
|
objectSrc: ['\'none\'']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
app.use(busboy());
|
app.use(busboy());
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use(express.static(STATIC_PATH));
|
app.use(express.static(STATIC_PATH));
|
||||||
|
@ -92,33 +116,35 @@ app.get('/assets/download/:id', (req, res) => {
|
||||||
storage
|
storage
|
||||||
.metadata(id)
|
.metadata(id)
|
||||||
.then(meta => {
|
.then(meta => {
|
||||||
storage.length(id).then(contentLength => {
|
storage
|
||||||
res.writeHead(200, {
|
.length(id)
|
||||||
'Content-Disposition': 'attachment; filename=' + meta.filename,
|
.then(contentLength => {
|
||||||
'Content-Type': 'application/octet-stream',
|
res.writeHead(200, {
|
||||||
'Content-Length': contentLength,
|
'Content-Disposition': 'attachment; filename=' + meta.filename,
|
||||||
'X-File-Metadata': JSON.stringify(meta)
|
'Content-Type': 'application/octet-stream',
|
||||||
});
|
'Content-Length': contentLength,
|
||||||
const file_stream = storage.get(id);
|
'X-File-Metadata': JSON.stringify(meta)
|
||||||
|
});
|
||||||
|
const file_stream = storage.get(id);
|
||||||
|
|
||||||
file_stream.on('end', () => {
|
file_stream.on('end', () => {
|
||||||
storage
|
storage
|
||||||
.forceDelete(id)
|
.forceDelete(id)
|
||||||
.then(err => {
|
.then(err => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
log.info('Deleted:', id);
|
log.info('Deleted:', id);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
log.info('DeleteError:', id);
|
log.info('DeleteError:', id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
file_stream.pipe(res);
|
file_stream.pipe(res);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
|
@ -157,15 +183,17 @@ app.post('/upload', (req, res, next) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
meta = JSON.parse(req.header('X-File-Metadata'));
|
meta = JSON.parse(req.header('X-File-Metadata'));
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
res.sendStatus(400);
|
res.sendStatus(400);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validateIV(meta.id) ||
|
if (
|
||||||
!meta.hasOwnProperty('aad') ||
|
!validateIV(meta.id) ||
|
||||||
!meta.hasOwnProperty('id') ||
|
!meta.hasOwnProperty('aad') ||
|
||||||
!meta.hasOwnProperty('filename')) {
|
!meta.hasOwnProperty('id') ||
|
||||||
|
!meta.hasOwnProperty('filename')
|
||||||
|
) {
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -216,4 +244,4 @@ const validateIV = route_id => {
|
||||||
module.exports = {
|
module.exports = {
|
||||||
server: server,
|
server: server,
|
||||||
storage: storage
|
storage: storage
|
||||||
}
|
};
|
||||||
|
|
|
@ -129,7 +129,9 @@ function localGet(id) {
|
||||||
|
|
||||||
function localSet(newId, file, filename, meta) {
|
function localSet(newId, file, filename, meta) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const fstream = fs.createWriteStream(path.join(__dirname, '../static', newId));
|
const fstream = fs.createWriteStream(
|
||||||
|
path.join(__dirname, '../static', newId)
|
||||||
|
);
|
||||||
file.pipe(fstream);
|
file.pipe(fstream);
|
||||||
fstream.on('close', () => {
|
fstream.on('close', () => {
|
||||||
redis_client.hmset(newId, meta);
|
redis_client.hmset(newId, meta);
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="download-page-one">
|
<div id="download-page-one">
|
||||||
<div>
|
<div>
|
||||||
<button id="download-btn" onclick="download()">Download File</button>
|
<button id="download-btn">Download File</button>
|
||||||
</div>
|
</div>
|
||||||
<div id='expired-img'>
|
<div id='expired-img'>
|
||||||
<img src='/resources/link_expired.png' />
|
<img src='/resources/link_expired.png' />
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<div class="title">
|
<div class="title">
|
||||||
Share your files quickly, privately and securely.
|
Share your files quickly, privately and securely.
|
||||||
</div>
|
</div>
|
||||||
<div class="upload-window" ondrop="onUpload(event)" ondragover="allowDrop(event)">
|
<div class="upload-window">
|
||||||
<div id="upload-img"><img src="/resources/upload.svg" alt="Upload"/></div>
|
<div id="upload-img"><img src="/resources/upload.svg" alt="Upload"/></div>
|
||||||
<div>
|
<div>
|
||||||
DRAG & DROP
|
DRAG & DROP
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
<div id="browse">
|
<div id="browse">
|
||||||
<form method="post" action="upload" enctype="multipart/form-data">
|
<form method="post" action="upload" enctype="multipart/form-data">
|
||||||
<label for="file-upload" class="file-upload">browse</label>
|
<label for="file-upload" class="file-upload">browse</label>
|
||||||
<input id="file-upload" type="file" onchange="onUpload(event)" name="fileUploaded" />
|
<input id="file-upload" type="file" name="fileUploaded" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue