Migrate the file to a sandboxed iframe

This commit is contained in:
yflory
2017-09-12 18:40:11 +02:00
parent 5ac3e97dc8
commit 437f50fd4a
21 changed files with 1697 additions and 317 deletions

Binary file not shown.

271
www/oldfile/file-crypto.js Normal file
View File

@@ -0,0 +1,271 @@
define([
'/bower_components/tweetnacl/nacl-fast.min.js',
], function () {
var Nacl = window.nacl;
var PARANOIA = true;
var plainChunkLength = 128 * 1024;
var cypherChunkLength = 131088;
var computeEncryptedSize = function (bytes, meta) {
var metasize = Nacl.util.decodeUTF8(JSON.stringify(meta)).length;
var chunks = Math.ceil(bytes / plainChunkLength);
return metasize + 18 + (chunks * 16) + bytes;
};
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);
};
var createNonce = function () {
return new Uint8Array(new Array(24).fill(0));
};
var increment = function (N) {
var l = N.length;
while (l-- > 1) {
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;
// 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');
}
}
};
var joinChunks = function (chunks) {
return new Blob(chunks);
};
var concatBuffer = function (a, b) { // TODO make this not so ugly
return new Uint8Array(slice(a).concat(slice(b)));
};
var fetchMetadata = function (src, cb) {
var done = false;
var CB = function (err, res) {
if (done) { return; }
done = true;
cb(err, res);
};
var xhr = new XMLHttpRequest();
xhr.open("GET", src, true);
xhr.setRequestHeader('Range', 'bytes=0-1');
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
if (/^4/.test('' + this.status)) { return CB('XHR_ERROR'); }
var res = new Uint8Array(xhr.response);
var size = decodePrefix(res);
var xhr2 = new XMLHttpRequest();
xhr2.open("GET", src, true);
xhr2.setRequestHeader('Range', 'bytes=2-' + (size + 2));
xhr2.responseType = 'arraybuffer';
xhr2.onload = function () {
if (/^4/.test('' + this.status)) { return CB('XHR_ERROR'); }
var res2 = new Uint8Array(xhr2.response);
var all = concatBuffer(res, res2);
CB(void 0, all);
};
xhr2.send(null);
};
xhr.send(null);
};
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; }
};
var fetchDecryptedMetadata = function (src, key, cb) {
if (typeof(src) !== 'string') {
return window.setTimeout(function () {
cb('NO_SOURCE');
});
}
fetchMetadata(src, function (e, buffer) {
if (e) { return cb(e); }
cb(void 0, decryptMetadata(buffer, key));
});
};
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));
};
var nonce = createNonce();
var i = 0;
var prefix = u8.subarray(0, 2);
var metadataLength = decodePrefix(prefix);
var res = {
metadata: undefined,
};
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) {
return window.setTimeout(function () {
done('E_METADATA_DECRYPTION');
});
}
if (!res.metadata) {
return void setTimeout(function () {
done('NO_METADATA');
});
}
var takeChunk = function (cb) {
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);
if (!plaintext) { return cb('DECRYPTION_ERROR'); }
_progress(end);
cb(void 0, plaintext);
};
var chunks = [];
var again = function () {
takeChunk(function (e, plaintext) {
if (e) {
return setTimeout(function () {
done(e);
});
}
if (plaintext) {
if ((2 + metadataLength + 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');
});
};
again();
};
// metadata
/* { filename: 'raccoon.jpg', type: 'image/jpeg' } */
var encrypt = function (u8, metadata, key) {
var nonce = createNonce();
// encode metadata
var plaintext = Nacl.util.decodeUTF8(JSON.stringify(metadata));
// if metadata is too large, drop the thumbnail.
if (plaintext.length > 65535) {
var temp = JSON.parse(JSON.stringify(metadata));
delete metadata.thumbnail;
plaintext = Nacl.util.decodeUTF8(JSON.stringify(temp));
}
var i = 0;
var state = 0;
var next = function (cb) {
if (state === 2) { return void cb(); }
var start;
var end;
var part;
var box;
if (state === 0) { // metadata...
part = new Uint8Array(plaintext);
box = Nacl.secretbox(part, nonce, key);
increment(nonce);
if (box.length > 65535) {
return void cb('METADATA_TOO_LARGE');
}
var prefixed = new Uint8Array(encodePrefix(box.length)
.concat(slice(box)));
state++;
return void cb(void 0, prefixed);
}
// encrypt the rest of the file...
start = i * plainChunkLength;
end = start + plainChunkLength;
part = u8.subarray(start, end);
box = Nacl.secretbox(part, nonce, key);
increment(nonce);
i++;
// regular data is done
if (i * plainChunkLength >= u8.length) { state = 2; }
return void cb(void 0, box);
};
return next;
};
return {
decrypt: decrypt,
encrypt: encrypt,
joinChunks: joinChunks,
computeEncryptedSize: computeEncryptedSize,
decryptMetadata: decryptMetadata,
fetchMetadata: fetchMetadata,
fetchDecryptedMetadata: fetchDecryptedMetadata,
};
});

130
www/oldfile/file.less Normal file
View File

@@ -0,0 +1,130 @@
@import "/customize/src/less/variables.less";
@import "/customize/src/less/mixins.less";
@button-border: 2px;
html, body {
margin: 0px;
height: 100%;
}
#toolbar {
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
}
body {
display: flex;
flex-flow: column;
}
#app {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
#app.ready {
//background: url('/customize/bg3.jpg') no-repeat center center;
background-size: cover;
background-position: center;
}
.cryptpad-toolbar {
padding: 0px;
display: inline-block;
}
#file, #dl {
display: block;
height: 100%;
width: 100%;
border: @button-border solid black;
}
.inputfile {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
media-tag {
img {
max-width: 100%;
max-height: ~"calc(100vh - 96px)";
}
}
#upload-form, #download-form {
padding: 0px;
margin: 0px;
position: relative;
width: 50vh;
height: 50vh;
display: block;
margin: 50px auto;
max-width: 80vw;
label {
line-height: ~"calc(50vh - 20px)";
text-align: center;
position: relative;
padding: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height: 50vh;
box-sizing: border-box;
}
}
#download-form {
label {
display: flex;
justify-content: center;
align-items: center;
white-space: normal;
word-wrap: break-word;
span {
width: 50vh;
max-width: 80vw;
text-align: center;
line-height: 1.5em;
}
}
}
.hovering {
background-color: rgba(255, 0, 115, 0.5) !important;
}
.block {
display: block;
}
.hidden {
display: none;
}
.inputfile + label {
//border: 2px solid black;
//background-color: rgba(50, 50, 50, .10);
display: block;
}
.inputfile:focus + label,
.inputfile + label:hover {
//background-color: rgba(50, 50, 50, 0.30);
}
#progress {
position: absolute;
top: 0;
left: 0;
height: 100%;
transition: width 200ms;
width: 0%;
max-width: 100%;
max-height: 100%;
background-color: rgba(255, 0, 115, 0.75);
z-index: 10000;
display: block;
}

30
www/oldfile/index.html Normal file
View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html class="cp pad">
<head>
<title>CryptPad</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#pad-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
</style>
</head>
<body>
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>

30
www/oldfile/inner.html Normal file
View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
<script async data-bootload="/file/inner.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>.loading-hidden, .loading-hidden * {display: none !important;}</style>
</head>
<body class="loading-hidden">
<div id="toolbar" class="toolbar-container"></div>
<div id="app">
<div id="upload-form" style="display: none;">
<input type="file" name="file" id="file" class="inputfile" />
<label for="file" class="btn btn-primary block unselectable" data-localization-title="upload_choose"
data-localization="upload_choose"></label>
</div>
<div id="download-form" style="display: none;">
<input type="button" name="dl" id="dl" class="inputfile" />
<label for="dl" class="btn btn-success block unselectable" data-localization-title="download_button"><span data-localization="download_button"></span></label>
<span class="block" id="progress"></span>
</div>
<div id="download-view" style="display: none;">
<media-tag id="encryptedFile"></media-tag>
</div>
<div id="feedback" class="block hidden">
</div>
</div>
</body>
</html>

14
www/oldfile/inner.js Normal file
View File

@@ -0,0 +1,14 @@
define([
'jquery',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/file/file.less',
'less!/customize/src/less/cryptpad.less',
'less!/customize/src/less/toolbar.less',
], function ($) {
$('.loading-hidden').removeClass('loading-hidden');
// dirty hack to get rid the flash of the lock background
setTimeout(function () {
$('#app').addClass('ready');
}, 100);
});

269
www/oldfile/main.js Normal file
View File

@@ -0,0 +1,269 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js',
'/common/toolbar2.js',
'/common/cryptpad-common.js',
'/common/visible.js',
'/common/notify.js',
'/file/file-crypto.js',
'/common/media-tag.js',
'/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less/cryptpad.less',
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify, FileCrypto, MediaTag) {
var Messages = Cryptpad.Messages;
var saveAs = window.saveAs;
var Nacl = window.nacl;
var APP = window.APP = {};
$(function () {
var andThen = function () {
var ifrw = $('#pad-iframe')[0].contentWindow;
var $iframe = $('#pad-iframe').contents();
var $appContainer = $iframe.find('#app');
var $form = $iframe.find('#upload-form');
var $dlform = $iframe.find('#download-form');
var $dlview = $iframe.find('#download-view');
var $label = $form.find('label');
var $dllabel = $dlform.find('label span');
var $progress = $iframe.find('#progress');
var $body = $iframe.find('body');
$body.on('dragover', function (e) { e.preventDefault(); });
$body.on('drop', function (e) { e.preventDefault(); });
Cryptpad.addLoadingScreen();
var Title;
var uploadMode = false;
var $bar = $iframe.find('.toolbar-container');
var secret;
var hexFileName;
if (window.location.hash) {
secret = Cryptpad.getSecrets();
if (!secret.keys) { throw new Error("You need a hash"); } // TODO
hexFileName = Cryptpad.base64ToHex(secret.channel);
} else {
uploadMode = true;
}
Title = Cryptpad.createTitle({}, function(){}, Cryptpad);
var displayed = ['useradmin', 'newpad', 'limit', 'upgrade'];
if (secret && hexFileName) {
displayed.push('fileshare');
}
var configTb = {
displayed: displayed,
ifrw: ifrw,
common: Cryptpad,
//hideDisplayName: true,
$container: $bar,
};
if (uploadMode) {
displayed.push('pageTitle');
configTb.pageTitle = Messages.upload_title;
}
var toolbar = APP.toolbar = Toolbar.create(configTb);
toolbar.$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
if (!uploadMode) {
var src = Cryptpad.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var key = Nacl.util.decodeBase64(cryptKey);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) { return void console.error(e); }
var title = document.title = metadata.name;
Title.updateTitle(title || Title.defaultTitle);
toolbar.addElement(['pageTitle'], {pageTitle: title});
console.error(metadata);
var displayFile = function (ev, sizeMb, CB) {
var called_back;
var cb = function (e) {
if (called_back) { return; }
called_back = true;
if (CB) { CB(e); }
};
var $mt = $dlview.find('media-tag');
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var hexFileName = Cryptpad.base64ToHex(secret.channel);
$mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName);
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
var rightsideDisplayed = false;
$(window.document).on('decryption', function (e) {
var decrypted = e.originalEvent;
if (decrypted.callback) {
decrypted.callback();
}
console.log(decrypted);
$dlview.show();
$dlform.hide();
var $dlButton = $dlview.find('media-tag button');
if (ev) { $dlButton.click(); }
if (!$dlButton.length) {
$appContainer.css('background', 'white');
}
$dlButton.addClass('btn btn-success');
var text = Messages.download_mt_button + '<br>';
text += '<b>' + Cryptpad.fixHTML(title) + '</b><br>';
text += '<em>' + Messages._getKey('formattedMB', [sizeMb]) + '</em>';
$dlButton.html(text);
if (!rightsideDisplayed) {
toolbar.$rightside.append(Cryptpad.createButton('export', true, {}, function () {
saveAs(decrypted.blob, decrypted.metadata.name);
}))
.append(Cryptpad.createButton('forget', true, {}, function () {
// not sure what to do here
}));
rightsideDisplayed = true;
}
// make pdfs big
var toolbarHeight = $iframe.find('#toolbar').height();
var $another_iframe = $iframe.find('media-tag iframe').css({
'height': 'calc(100vh - ' + toolbarHeight + 'px)',
'width': '100vw',
'position': 'absolute',
'bottom': 0,
'left': 0,
'border': 0
});
if ($another_iframe.length) {
$another_iframe.load(function () {
cb();
});
} else {
cb();
}
})
.on('decryptionError', function (e) {
var error = e.originalEvent;
//Cryptpad.alert(error.message);
cb(error.message);
})
.on('decryptionProgress', function (e) {
var progress = e.originalEvent;
var p = progress.percent +'%';
$progress.width(p);
console.log(progress.percent);
});
/**
* Allowed mime types that have to be set for a rendering after a decryption.
*
* @type {Array}
*/
var allowedMediaTypes = [
'image/png',
'image/jpeg',
'image/jpg',
'image/gif',
'audio/mp3',
'audio/ogg',
'audio/wav',
'audio/webm',
'video/mp4',
'video/ogg',
'video/webm',
'application/pdf',
'application/dash+xml',
'download'
];
MediaTag.CryptoFilter.setAllowedMediaTypes(allowedMediaTypes);
MediaTag($mt[0]);
};
var todoBigFile = function (sizeMb) {
$dlform.show();
Cryptpad.removeLoadingScreen();
$dllabel.append($('<br>'));
$dllabel.append(Cryptpad.fixHTML(metadata.name));
// don't display the size if you don't know it.
if (typeof(sizeM) === 'number') {
$dllabel.append($('<br>'));
$dllabel.append(Messages._getKey('formattedMB', [sizeMb]));
}
var decrypting = false;
var onClick = function (ev) {
if (decrypting) { return; }
decrypting = true;
displayFile(ev, sizeMb, function (err) {
if (err) { Cryptpad.alert(err); }
});
};
if (typeof(sizeMb) === 'number' && sizeMb < 5) { return void onClick(); }
$dlform.find('#dl, #progress').click(onClick);
};
Cryptpad.getFileSize(window.location.href, function (e, data) {
if (e) {
return void Cryptpad.errorLoadingScreen(e);
}
var size = Cryptpad.bytesToMegabytes(data);
return void todoBigFile(size);
});
});
return;
}
if (!Cryptpad.isLoggedIn()) {
return Cryptpad.alert(Messages.upload_mustLogin, function () {
if (sessionStorage) {
sessionStorage.redirectTo = window.location.href;
}
window.location.href = '/login/';
});
}
$form.css({
display: 'block',
});
var fmConfig = {
dropArea: $form,
hoverArea: $label,
body: $body,
keepTable: true // Don't fadeOut the tbale with the uploaded files
};
var FM = Cryptpad.createFileManager(fmConfig);
$form.find("#file").on('change', function (e) {
var file = e.target.files[0];
FM.handleFile(file);
});
// we're in upload mode
Cryptpad.removeLoadingScreen();
};
Cryptpad.ready(function () {
andThen();
Cryptpad.reportAppUsage();
});
});
});

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html class="cp pad">
<head>
<title>CryptPad</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
<link rel="icon" type="image/png"
href="/customize/main-favicon.png"
data-main-favicon="/customize/main-favicon.png"
data-alt-favicon="/customize/alt-favicon.png"
id="favicon" />
<link rel="stylesheet" href="/customize/main.css" />
</head>
<body>

89
www/oldfile/test/main.js Normal file
View File

@@ -0,0 +1,89 @@
define([
'/api/config',
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js',
'/common/toolbar.js',
'/common/cryptpad-common.js',
'/common/visible.js',
'/common/notify.js',
'/file/file-crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'/bower_components/file-saver/FileSaver.min.js',
], function (Config, $, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify, FileCrypto) {
var urlArgs = Config.requireConf.urlArgs;
var Nacl = window.nacl;
$(function () {
var filesAreSame = function (a, b) {
var l = a.length;
if (l !== b.length) { return false; }
var i = 0;
for (; i < l; i++) { if (a[i] !== b[i]) { return false; } }
return true;
};
var metadataIsSame = function (A, B) {
return !Object.keys(A).some(function (k) {
return A[k] !== B[k];
});
};
var upload = function (blob, metadata) {
var u8 = new Uint8Array(blob);
var key = Nacl.randomBytes(32);
var next = FileCrypto.encrypt(u8, metadata, key);
var chunks = [];
var sendChunk = function (box, cb) {
chunks.push(box);
cb();
};
var again = function (err, box) {
if (err) { throw new Error(err); }
if (box) {
return void sendChunk(box, function (e) {
if (e) {
console.error(e);
return Cryptpad.alert('Something went wrong');
}
next(again);
});
}
// check if the uploaded file can be decrypted
var newU8 = FileCrypto.joinChunks(chunks);
console.log('encrypted file with metadata is %s uint8s', newU8.length);
FileCrypto.decrypt(newU8, key, function (e, res) {
if (e) { return Cryptpad.alert(e); }
if (filesAreSame(blob, res.content) &&
metadataIsSame(res.metadata, metadata)) {
Cryptpad.alert("successfully uploaded");
} else {
Cryptpad.alert('encryption failure!');
}
});
};
next(again);
};
var andThen = function () {
var src = '/customize/cryptofist_mini.png?' + urlArgs;
Cryptpad.fetch(src, function (e, file) {
console.log('original file is %s uint8s', file.length);
upload(file, {
pew: 'pew',
bang: 'bang',
});
});
};
andThen();
});
});