Password-protected files

pull/1/head
yflory 7 years ago
parent 95218f0fa1
commit c7e08fedfb

@ -331,14 +331,11 @@ define([
dropArea: $('.CodeMirror'), dropArea: $('.CodeMirror'),
body: $('body'), body: $('body'),
onUploaded: function (ev, data) { onUploaded: function (ev, data) {
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = Hash.getBlobPathFromHex(secret.channel);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>'; var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
editor.replaceSelection(mt); editor.replaceSelection(mt);
} }
}; };

@ -11,6 +11,7 @@ define([
var uint8ArrayToHex = Util.uint8ArrayToHex; var uint8ArrayToHex = Util.uint8ArrayToHex;
var hexToBase64 = Util.hexToBase64; var hexToBase64 = Util.hexToBase64;
var base64ToHex = Util.base64ToHex; var base64ToHex = Util.base64ToHex;
Hash.encodeBase64 = Nacl.util.encodeBase64;
// This implementation must match that on the server // This implementation must match that on the server
// it's used for a checksum // it's used for a checksum
@ -59,6 +60,11 @@ define([
return '/1/' + hexToBase64(secret.channel) + '/' + return '/1/' + hexToBase64(secret.channel) + '/' +
Crypto.b64RemoveSlashes(data.fileKeyStr) + '/'; Crypto.b64RemoveSlashes(data.fileKeyStr) + '/';
} }
if (version === 2) {
if (!data.fileKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/' + Crypto.b64RemoveSlashes(data.fileKeyStr) + '/' + pass;
}
}; };
// V1 // V1
@ -95,12 +101,22 @@ define([
}; };
Hash.createRandomHash = function (type, password) { Hash.createRandomHash = function (type, password) {
var cryptor = Crypto.createEditCryptor2(void 0, void 0, password); var cryptor;
if (type === 'file') {
cryptor = Crypto.createFileCryptor2(void 0, password);
return getFileHashFromKeys({
password: Boolean(password),
version: 2,
type: type,
keys: cryptor.fileKeyStr
});
}
cryptor = Crypto.createEditCryptor2(void 0, void 0, password);
return getEditHashFromKeys({ return getEditHashFromKeys({
password: Boolean(password), password: Boolean(password),
version: 2, version: 2,
type: type, type: type,
keys: { editKeyStr: cryptor.editKeyStr } keys: cryptor.editKeyStr
}); });
}; };
@ -113,6 +129,7 @@ Version 1
var parseTypeHash = Hash.parseTypeHash = function (type, hash) { var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
if (!hash) { return; } if (!hash) { return; }
var options;
var parsed = {}; var parsed = {};
var hashArr = fixDuplicateSlashes(hash).split('/'); var hashArr = fixDuplicateSlashes(hash).split('/');
if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) { if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
@ -125,7 +142,6 @@ Version 1
parsed.version = 0; parsed.version = 0;
return parsed; return parsed;
} }
var options;
if (hashArr[1] && hashArr[1] === '1') { // Version 1 if (hashArr[1] && hashArr[1] === '1') { // Version 1
parsed.version = 1; parsed.version = 1;
parsed.mode = hashArr[2]; parsed.mode = hashArr[2];
@ -175,6 +191,25 @@ Version 1
parsed.key = hashArr[3].replace(/-/g, '/'); parsed.key = hashArr[3].replace(/-/g, '/');
return parsed; return parsed;
} }
if (hashArr[1] && hashArr[1] === '2') { // Version 2
parsed.version = 2;
parsed.app = hashArr[2];
parsed.key = hashArr[3];
options = hashArr.slice(4);
parsed.password = options.indexOf('p') !== -1;
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 4).join('/') + '/';
if (parsed.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed;
}
return parsed; return parsed;
} }
if (['user'].indexOf(type) !== -1) { if (['user'].indexOf(type) !== -1) {
@ -309,11 +344,12 @@ Version 1
} }
} }
} else if (parsed.type === "file") { } else if (parsed.type === "file") {
// version 2 hashes are to be used for encrypted blobs secret.channel = base64ToHex(parsed.channel);
secret.channel = parsed.channel; secret.keys = {
secret.keys = { fileKeyStr: parsed.key }; fileKeyStr: parsed.key,
cryptKey: Nacl.util.decodeBase64(parsed.key)
};
} else if (parsed.type === "user") { } else if (parsed.type === "user") {
// version 2 hashes are to be used for encrypted blobs
throw new Error("User hashes can't be opened (yet)"); throw new Error("User hashes can't be opened (yet)");
} }
} else if (parsed.version === 2) { } else if (parsed.version === 2) {
@ -338,7 +374,12 @@ Version 1
} }
} }
} else if (parsed.type === "file") { } else if (parsed.type === "file") {
throw new Error("File hashes should be version 1"); secret.channel = base64ToHex(secret.keys.chanId);
secret.keys = Crypto.createFileCryptor2(parsed.key, password);
secret.key = secret.keys.fileKeyStr;
if (secret.channel.length !== 48 || secret.key.length !== 24) {
throw new Error("The channel key and/or the encryption key is invalid");
}
} else if (parsed.type === "user") { } else if (parsed.type === "user") {
throw new Error("User hashes can't be opened (yet)"); throw new Error("User hashes can't be opened (yet)");
} }

@ -250,17 +250,15 @@ define([
var k = getKey(parsed.type, channel); var k = getKey(parsed.type, channel);
common.setThumbnail(k, b64, cb); common.setThumbnail(k, b64, cb);
}; };
Thumb.displayThumbnail = function (common, href, channel, $container, cb) { Thumb.displayThumbnail = function (common, href, channel, password, $container, cb) {
cb = cb || function () {}; cb = cb || function () {};
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
var k = getKey(parsed.type, channel); var k = getKey(parsed.type, channel);
var whenNewThumb = function () { var whenNewThumb = function () {
// PASSWORD_FILES var secret = Hash.getSecrets('file', parsed.hash, password);
var secret = Hash.getSecrets('file', parsed.hash); var hexFileName = channel;
var hexFileName = Util.base64ToHex(secret.channel);
var src = Hash.getBlobPathFromHex(hexFileName); var src = Hash.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr; var key = secret.keys && secret.keys.cryptKey;
var key = Nacl.util.decodeBase64(cryptKey);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) { if (e) {
if (e === 'XHR_ERROR') { return; } if (e === 'XHR_ERROR') { return; }

@ -1169,8 +1169,8 @@ define([
// No password for avatars // No password for avatars
var secret = Hash.getSecrets('file', parsed.hash); var secret = Hash.getSecrets('file', parsed.hash);
if (secret.keys && secret.channel) { if (secret.keys && secret.channel) {
var cryptKey = secret.keys && secret.keys.fileKeyStr; var hexFileName = secret.channel;
var hexFileName = Util.base64ToHex(secret.channel); var cryptKey = Hash.encodeBase64(secret.keys && secret.keys.cryptKey);
var src = Hash.getBlobPathFromHex(hexFileName); var src = Hash.getBlobPathFromHex(hexFileName);
Common.getFileSize(hexFileName, function (e, data) { Common.getFileSize(hexFileName, function (e, data) {
if (e || !data) { if (e || !data) {

@ -578,7 +578,6 @@ define([
} }
var parsed = Hash.parsePadUrl(window.location.href); var parsed = Hash.parsePadUrl(window.location.href);
if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); } if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); }
if (parsed.type === 'file' && typeof(parsed.channel) === 'string') { secret.channel = Util.base64ToHex(secret.channel); }
hashes = Hash.getHashes(secret); hashes = Hash.getHashes(secret);
if (secret.version === 0) { if (secret.version === 0) {

@ -41,11 +41,15 @@ define([
}; };
renderer.image = function (href, title, text) { renderer.image = function (href, title, text) {
if (href.slice(0,6) === '/file/') { if (href.slice(0,6) === '/file/') {
// PASSWORD_FILES // DEPRECATED
// Mediatag using markdown syntax should not be used anymore so they don't support
// password-protected files
console.log('DEPRECATED: mediatag using markdown syntax!');
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var secret = Hash.getSecrets('file', parsed.hash);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = Hash.getBlobPathFromHex(secret.channel);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '">'; var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
if (mediaMap[src]) { if (mediaMap[src]) {
mt += mediaMap[src]; mt += mediaMap[src];
} }

@ -115,13 +115,8 @@ define([
parsed = Hash.parsePadUrl(el.href); parsed = Hash.parsePadUrl(el.href);
if (!el.href) { return; } if (!el.href) { return; }
if (!el.channel) { if (!el.channel) {
if (parsed.hashData && parsed.hashData.type === "file") {
// PASSWORD_FILES
el.channel = Util.base64ToHex(parsed.hashData.channel);
} else {
var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.channel = secret.channel; el.channel = secret.channel;
}
progress(6, Math.round(100*i/padsLength)); progress(6, Math.round(100*i/padsLength));
console.log('Adding missing channel in filesData ', el.channel); console.log('Adding missing channel in filesData ', el.channel);
} }

@ -92,7 +92,7 @@ define([
var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null; var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null;
if (profileChan) { list.push(profileChan); } if (profileChan) { list.push(profileChan); }
var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar, null) : null; var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar, null) : null;
if (avatarChan) { list.push(Util.base64ToHex(avatarChan)); } if (avatarChan) { list.push(avatarChan); }
} }
if (store.proxy.friends) { if (store.proxy.friends) {

@ -13,7 +13,16 @@ define([
// if it exists, path contains the new pad location in the drive // if it exists, path contains the new pad location in the drive
var path = file.path; var path = file.path;
var key = Nacl.randomBytes(32); // XXX
// PASSWORD_FILES
var password;
var hash = Hash.createRandomHash('file', password);
var secret = Hash.getSecrets('file', hash, password);
var key = secret.keys.cryptKey;
var id = secret.channel;
//var key = Nacl.randomBytes(32);
// XXX provide channel id to "next"
var next = FileCrypto.encrypt(u8, metadata, key); var next = FileCrypto.encrypt(u8, metadata, key);
var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata); var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
@ -44,21 +53,11 @@ define([
} }
// if not box then done // if not box then done
common.uploadComplete(function (e, id) { common.uploadComplete(function (e/*, id*/) { // XXX id is given, not asked
if (e) { return void console.error(e); } if (e) { return void console.error(e); }
var uri = ['', 'blob', id.slice(0,2), id].join('/'); var uri = ['', 'blob', id.slice(0,2), id].join('/');
console.log("encrypted blob is now available as %s", uri); console.log("encrypted blob is now available as %s", uri);
var b64Key = Nacl.util.encodeBase64(key);
var secret = {
version: 1,
channel: id,
keys: {
fileKeyStr: b64Key
}
};
var hash = Hash.getFileHashFromKeys(secret);
var href = '/file/#' + hash; var href = '/file/#' + hash;
var title = metadata.name; var title = metadata.name;

@ -589,13 +589,8 @@ define([
// Fix channel // Fix channel
if (!el.channel) { if (!el.channel) {
try { try {
if (parsed.hashData && parsed.hashData.type === "file") {
// PASSWORD_FILES
el.channel = Util.base64ToHex(parsed.hashData.channel);
} else {
var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.channel = secret.channel; el.channel = secret.channel;
}
console.log('Adding missing channel in filesData ', el.channel); console.log('Adding missing channel in filesData ', el.channel);
} catch (e) { } catch (e) {
console.error(e); console.error(e);

@ -329,15 +329,11 @@ define([
dropArea: $('.CodeMirror'), dropArea: $('.CodeMirror'),
body: $('body'), body: $('body'),
onUploaded: function (ev, data) { onUploaded: function (ev, data) {
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = Hash.getBlobPathFromHex(secret.channel);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + var key = Hash.encodeBase64(secret.keys.cryptKey);
parsed.hashData.key + '"></media-tag>'; var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
editor.replaceSelection(mt); editor.replaceSelection(mt);
} }
}; };

@ -113,16 +113,16 @@ define([
return '<script src="' + origin + '/common/media-tag-nacl.min.js"></script>'; return '<script src="' + origin + '/common/media-tag-nacl.min.js"></script>';
}; };
funcs.getMediatagFromHref = function (href) { funcs.getMediatagFromHref = function (href) {
// PASSWORD_FILES // XXX: Should only be used with the current href
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('file', parsed.hash);
var data = ctx.metadataMgr.getPrivateData(); var data = ctx.metadataMgr.getPrivateData();
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('file', parsed.hash, data.password);
if (secret.keys && secret.channel) { if (secret.keys && secret.channel) {
var cryptKey = secret.keys && secret.keys.fileKeyStr; var key = Hash.encodeBase64(secret.keys && secret.keys.cryptKey);
var hexFileName = Util.base64ToHex(secret.channel); var hexFileName = secret.channel;
var origin = data.fileHost || data.origin; var origin = data.fileHost || data.origin;
var src = origin + Hash.getBlobPathFromHex(hexFileName); var src = origin + Hash.getBlobPathFromHex(hexFileName);
return '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + cryptKey + '">' + return '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '">' +
'</media-tag>'; '</media-tag>';
} }
return; return;

@ -590,7 +590,6 @@ define([
} }
}, cb); }, cb);
} }
console.log(path, newName);
if (path.length <= 1) { if (path.length <= 1) {
logError('Renaming `root` is forbidden'); logError('Renaming `root` is forbidden');
return; return;

@ -1305,7 +1305,7 @@ define([
$span.attr('title', name); $span.attr('title', name);
var type = Messages.type[hrefData.type] || hrefData.type; var type = Messages.type[hrefData.type] || hrefData.type;
common.displayThumbnail(data.href, data.channel, $span, function ($thumb) { common.displayThumbnail(data.href, data.channel, data.password, $span, function ($thumb) {
// Called only if the thumbnail exists // Called only if the thumbnail exists
// Remove the .hide() added by displayThumnail() because it hides the icon in // Remove the .hide() added by displayThumnail() because it hides the icon in
// list mode too // list mode too

@ -54,17 +54,14 @@ define([
var uploadMode = false; var uploadMode = false;
var secret; var secret;
var hexFileName;
var metadataMgr = common.getMetadataMgr(); var metadataMgr = common.getMetadataMgr();
var priv = metadataMgr.getPrivateData(); var priv = metadataMgr.getPrivateData();
if (!priv.filehash) { if (!priv.filehash) {
uploadMode = true; uploadMode = true;
} else { } else {
// PASSWORD_FILES secret = Hash.getSecrets('file', priv.filehash, priv.password);
secret = Hash.getSecrets('file', priv.filehash);
if (!secret.keys) { throw new Error("You need a hash"); } if (!secret.keys) { throw new Error("You need a hash"); }
hexFileName = Util.base64ToHex(secret.channel);
} }
var Title = common.createTitle({}); var Title = common.createTitle({});
@ -87,9 +84,10 @@ define([
toolbar.$rightside.html(''); toolbar.$rightside.html('');
if (!uploadMode) { if (!uploadMode) {
var hexFileName = secret.channel;
var src = Hash.getBlobPathFromHex(hexFileName); var src = Hash.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr; var key = secret.keys && secret.keys.cryptKey;
var key = Nacl.util.decodeBase64(cryptKey); var cryptKey = Nacl.util.encodeBase64(key);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) { if (e) {
@ -118,9 +116,7 @@ define([
}; };
var $mt = $dlview.find('media-tag'); var $mt = $dlview.find('media-tag');
var cryptKey = secret.keys && secret.keys.fileKeyStr; $mt.attr('src', src);
var hexFileName = Util.base64ToHex(secret.channel);
$mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName);
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey); $mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
var rightsideDisplayed = false; var rightsideDisplayed = false;
@ -263,7 +259,7 @@ define([
dropArea: $form, dropArea: $form,
hoverArea: $label, hoverArea: $label,
body: $body, body: $body,
keepTable: true // Don't fadeOut the tbale with the uploaded files keepTable: true // Don't fadeOut the table with the uploaded files
}; };
var FM = common.createFileManager(fmConfig); var FM = common.createFileManager(fmConfig);

@ -40,14 +40,14 @@ define([
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
hideFileDialog(); hideFileDialog();
if (parsed.type === 'file') { if (parsed.type === 'file') {
// PASSWORD_FILES var secret = Hash.getSecrets('file', parsed.hash, data.password);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var src = Hash.getBlobPathFromHex(secret.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var key = Hash.encodeBase64(secret.keys.cryptKey);
sframeChan.event("EV_FILE_PICKED", { sframeChan.event("EV_FILE_PICKED", {
type: parsed.type, type: parsed.type,
src: src, src: src,
name: data.name, name: data.name,
key: parsed.hashData.key key: key
}); });
return; return;
} }
@ -69,8 +69,8 @@ define([
APP.FM = common.createFileManager(fmConfig); APP.FM = common.createFileManager(fmConfig);
// Create file picker // Create file picker
var onSelect = function (url, name) { var onSelect = function (url, name, password) {
onFilePicked({url: url, name: name}); onFilePicked({url: url, name: name, password: password});
}; };
var data = { var data = {
FM: APP.FM FM: APP.FM
@ -135,11 +135,13 @@ define([
$('<span>', {'class': 'cp-filepicker-content-element-name'}).text(name) $('<span>', {'class': 'cp-filepicker-content-element-name'}).text(name)
.appendTo($span); .appendTo($span);
$span.click(function () { $span.click(function () {
if (typeof onSelect === "function") { onSelect(data.href, name); } if (typeof onSelect === "function") {
onSelect(data.href, name, data.password);
}
}); });
// Add thumbnail if it exists // Add thumbnail if it exists
common.displayThumbnail(data.href, data.channel, $span); common.displayThumbnail(data.href, data.channel, data.password, $span);
}); });
$input.focus(); $input.focus();
}; };

@ -552,11 +552,11 @@ define([
ckeditor: editor, ckeditor: editor,
body: $('body'), body: $('body'),
onUploaded: function (ev, data) { onUploaded: function (ev, data) {
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = Hash.getBlobPathFromHex(secret.channel);
var mt = '<media-tag contenteditable="false" src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '" tabindex="1"></media-tag>'; var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag contenteditable="false" src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
// MEDIATAG // MEDIATAG
var element = window.CKEDITOR.dom.element.createFromHtml(mt); var element = window.CKEDITOR.dom.element.createFromHtml(mt);
editor.insertElement(element); editor.insertElement(element);

@ -500,11 +500,11 @@ define([
dropArea: $('.CodeMirror'), dropArea: $('.CodeMirror'),
body: $('body'), body: $('body'),
onUploaded: function (ev, data) { onUploaded: function (ev, data) {
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = Hash.getBlobPathFromHex(secret.channel);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>'; var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
editor.replaceSelection(mt); editor.replaceSelection(mt);
} }
}; };

Loading…
Cancel
Save