Password-protected files

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

@ -331,14 +331,11 @@ define([
dropArea: $('.CodeMirror'),
body: $('body'),
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 hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
editor.replaceSelection(mt);
}
};

@ -11,6 +11,7 @@ define([
var uint8ArrayToHex = Util.uint8ArrayToHex;
var hexToBase64 = Util.hexToBase64;
var base64ToHex = Util.base64ToHex;
Hash.encodeBase64 = Nacl.util.encodeBase64;
// This implementation must match that on the server
// it's used for a checksum
@ -59,6 +60,11 @@ define([
return '/1/' + hexToBase64(secret.channel) + '/' +
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
@ -95,12 +101,22 @@ define([
};
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({
password: Boolean(password),
version: 2,
type: type,
keys: { editKeyStr: cryptor.editKeyStr }
keys: cryptor.editKeyStr
});
};
@ -113,6 +129,7 @@ Version 1
var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
if (!hash) { return; }
var options;
var parsed = {};
var hashArr = fixDuplicateSlashes(hash).split('/');
if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
@ -125,7 +142,6 @@ Version 1
parsed.version = 0;
return parsed;
}
var options;
if (hashArr[1] && hashArr[1] === '1') { // Version 1
parsed.version = 1;
parsed.mode = hashArr[2];
@ -175,6 +191,25 @@ Version 1
parsed.key = hashArr[3].replace(/-/g, '/');
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;
}
if (['user'].indexOf(type) !== -1) {
@ -309,11 +344,12 @@ Version 1
}
}
} else if (parsed.type === "file") {
// version 2 hashes are to be used for encrypted blobs
secret.channel = parsed.channel;
secret.keys = { fileKeyStr: parsed.key };
secret.channel = base64ToHex(parsed.channel);
secret.keys = {
fileKeyStr: parsed.key,
cryptKey: Nacl.util.decodeBase64(parsed.key)
};
} 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)");
}
} else if (parsed.version === 2) {
@ -338,7 +374,12 @@ Version 1
}
}
} 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") {
throw new Error("User hashes can't be opened (yet)");
}

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

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

@ -578,7 +578,6 @@ define([
}
var parsed = Hash.parsePadUrl(window.location.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);
if (secret.version === 0) {

@ -41,11 +41,15 @@ define([
};
renderer.image = function (href, title, text) {
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 hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '">';
var secret = Hash.getSecrets('file', parsed.hash);
var src = Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
if (mediaMap[src]) {
mt += mediaMap[src];
}

@ -115,13 +115,8 @@ define([
parsed = Hash.parsePadUrl(el.href);
if (!el.href) { return; }
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);
el.channel = secret.channel;
}
var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.channel = secret.channel;
progress(6, Math.round(100*i/padsLength));
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;
if (profileChan) { list.push(profileChan); }
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) {

@ -13,7 +13,16 @@ define([
// if it exists, path contains the new pad location in the drive
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 estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
@ -44,21 +53,11 @@ define([
}
// 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); }
var uri = ['', 'blob', id.slice(0,2), id].join('/');
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 title = metadata.name;

@ -589,14 +589,9 @@ define([
// Fix channel
if (!el.channel) {
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);
el.channel = secret.channel;
}
console.log('Adding missing channel in filesData ', el.channel);
console.log('Adding missing channel in filesData ', el.channel);
} catch (e) {
console.error(e);
}

@ -329,15 +329,11 @@ define([
dropArea: $('.CodeMirror'),
body: $('body'),
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 hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' +
parsed.hashData.key + '"></media-tag>';
var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
editor.replaceSelection(mt);
}
};

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

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

@ -1305,7 +1305,7 @@ define([
$span.attr('title', name);
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
// Remove the .hide() added by displayThumnail() because it hides the icon in
// list mode too

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

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

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

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

Loading…
Cancel
Save