initial refactoring for support of version 2 paste format, some cleanup on the side

This commit is contained in:
El RIDO 2018-12-25 17:34:39 +01:00
parent f3165f0cab
commit 0ab06e34ec
No known key found for this signature in database
GPG Key ID: 0F5C940A6BD81F92
4 changed files with 231 additions and 282 deletions

View File

@ -701,17 +701,25 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* @private * @private
* @param {string} key * @param {string} key
* @param {string} password * @param {string} password
* @param {object} object cryptographic message * @param {array} spec cryptographic specification
* @return {CryptoKey} derived key * @return {CryptoKey} derived key
*/ */
async function deriveKey(key, password, object) async function deriveKey(key, password, spec)
{ {
let keyArray = StrToArr(key); let keyArray = StrToArr(key);
if ((password || '').trim().length > 0) { if ((password || '').trim().length > 0) {
keyArray += await window.crypto.subtle.digest( let passwordBuffer = await window.crypto.subtle.digest(
{name: 'SHA-256'}, {name: 'SHA-256'},
StrToArr(password) StrToArr(utob(password))
); );
let hexHash = Array.prototype.map.call(
new Uint8Array(passwordBuffer), x => ('00' + x.toString(16)).slice(-2)
).join('');
let passwordArray = StrToArr(hexHash),
newKeyArray = new Uint8Array(keyArray.length + passwordArray.length);
newKeyArray.set(keyArray, 0);
newKeyArray.set(passwordArray, keyArray.length);
keyArray = newKeyArray;
} }
// import raw key // import raw key
@ -724,39 +732,40 @@ jQuery.PrivateBin = (function($, RawDeflate) {
); );
// derive a stronger key for use with AES // derive a stronger key for use with AES
return await window.crypto.subtle.deriveKey( return window.crypto.subtle.deriveKey(
{ {
name: 'PBKDF2', // we use PBKDF2 for key derivation name: 'PBKDF2', // we use PBKDF2 for key derivation
salt: StrToArr(atob(object.salt)), // salt used in HMAC salt: StrToArr(spec[1]), // salt used in HMAC
iterations: object.iter, // amount of iterations to apply iterations: spec[2], // amount of iterations to apply
hash: {name: 'SHA-256'} // can be "SHA-1", "SHA-256", "SHA-384" or "SHA-512" hash: {name: 'SHA-256'} // can be "SHA-1", "SHA-256", "SHA-384" or "SHA-512"
}, },
importedKey, importedKey,
{ {
name: 'AES-' + object.mode.toUpperCase(), // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC") name: 'AES-' + spec[6].toUpperCase(), // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC")
length: object.ks // can be 128, 192 or 256 length: spec[3] // can be 128, 192 or 256
}, },
false, // the key may not be exported false, // the key may not be exported
['encrypt'] // we may only use it for decryption ['encrypt', 'decrypt'] // we use it for de- and encryption
); );
} }
/** /**
* gets crypto settings from given object * gets crypto settings from specification and authenticated data
* *
* @name CryptTool.cryptoSettings * @name CryptTool.cryptoSettings
* @function * @function
* @private * @private
* @param {object} object cryptographic message * @param {string} adata authenticated data
* @param {array} spec cryptographic specification
* @return {object} crypto settings * @return {object} crypto settings
*/ */
function cryptoSettings(object) function cryptoSettings(adata, spec)
{ {
return { return {
name: 'AES-' + object.mode.toUpperCase(), // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC") name: 'AES-' + spec[6].toUpperCase(), // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC")
iv: StrToArr(atob(object.iv)), // the initialization vector you used to encrypt iv: StrToArr(spec[0]), // the initialization vector you used to encrypt
additionalData: StrToArr(atob(object.adata)), // the addtional data you used during encryption (if any) additionalData: StrToArr(adata), // the addtional data you used during encryption (if any)
tagLength: object.ts // the length of the tag you used to encrypt (if any) tagLength: spec[4] // the length of the tag you used to encrypt (if any)
}; };
} }
@ -769,32 +778,53 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* @param {string} key * @param {string} key
* @param {string} password * @param {string} password
* @param {string} message * @param {string} message
* @return {string} data - JSON with encrypted data * @param {array} adata
* @return {array} encrypted message & adata containing encryption spec
*/ */
me.cipher = async function(key, password, message) me.cipher = async function(key, password, message, adata)
{ {
// AES in Galois Counter Mode, keysize 256 bit, authentication tag 128 bit, 10000 iterations in key derivation // AES in Galois Counter Mode, keysize 256 bit,
const iv = getRandomBytes(16); // authentication tag 128 bit, 10000 iterations in key derivation
let object = { const spec = [
iv: btoa(iv), getRandomBytes(16), // initialization vector
v: 1, getRandomBytes(8), // salt
iter: 10000, 10000, // iterations
ks: 256, 256, // key size
ts: 128, 128, // tag size
mode: 'gcm', 'aes', // algorithm
adata: '', // if used, base64 encode it with btoa() 'gcm', // algorithm mode
cipher: 'aes', 'none' // compression
salt: btoa(getRandomBytes(8)) ], encodedSpec = [
}; btoa(spec[0]),
btoa(spec[1]),
spec[2],
spec[3],
spec[4],
spec[5],
spec[6],
spec[7]
];
if (adata.length === 0) {
// comment
adata = encodedSpec;
} else if (adata[0] === null) {
// paste
adata[0] = encodedSpec;
}
// finally, encrypt message // finally, encrypt message
const encrypted = await window.crypto.subtle.encrypt( return [
cryptoSettings(object), btoa(
await deriveKey(key, password, object), ArrToStr(
StrToArr(compress(message)) // compressed plain text to encrypt await window.crypto.subtle.encrypt(
); cryptoSettings(JSON.stringify(adata), spec),
object.ct = btoa(ArrToStr(encrypted)); await deriveKey(key, password, spec),
return JSON.stringify(object); StrToArr(utob(message))
)
)
),
adata
];
}; };
/** /**
@ -805,25 +835,57 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* @function * @function
* @param {string} key * @param {string} key
* @param {string} password * @param {string} password
* @param {string} data - JSON with encrypted data * @param {string|object} data encrypted message
* @return {string} decrypted message, empty if decryption failed * @return {string} decrypted message, empty if decryption failed
*/ */
me.decipher = async function(key, password, data) me.decipher = async function(key, password, data)
{ {
let adataString, encodedSpec, compression, cipherMessage;
if (data instanceof Array) {
// version 2
adataString = JSON.stringify(data[1]);
encodedSpec = (data[1][0] instanceof Array ? data[1][0] : data[1]);
cipherMessage = data[0];
} else if (typeof data === 'string') {
// version 1
let object = JSON.parse(data);
adataString = atob(object.adata);
encodedSpec = [
object.iv,
object.salt,
object.iter,
object.ks,
object.ts,
object.cipher,
object.mode,
'rawdeflate'
];
cipherMessage = object.ct;
} else {
throw 'unsupported message format';
}
compression = encodedSpec[7];
let spec = encodedSpec, plainText = '';
spec[0] = atob(spec[0]);
spec[1] = atob(spec[1]);
try { try {
const object = JSON.parse(data); plainText = ArrToStr(
return decompress( await window.crypto.subtle.decrypt(
ArrToStr( cryptoSettings(adataString, spec),
await window.crypto.subtle.decrypt( await deriveKey(key, password, spec),
cryptoSettings(object), StrToArr(atob(cipherMessage))
await deriveKey(key, password, object),
StrToArr(atob(object.ct)) // cipher text to decrypt
)
) )
); );
} catch(err) { } catch(err) {
return ''; return '';
} }
if (compression === 'none') {
return btou(plainText);
} else if (compression === 'rawdeflate') {
return decompress(plainText);
} else {
throw 'unsupported compression format';
}
}; };
/** /**
@ -906,25 +968,25 @@ jQuery.PrivateBin = (function($, RawDeflate) {
} }
// reload data // reload data
Uploader.prepare(); ServerInteraction.prepare();
Uploader.setUrl(Helper.baseUri() + '?' + me.getPasteId()); ServerInteraction.setUrl(Helper.baseUri() + '?' + me.getPasteId());
Uploader.setFailure(function (status, data) { ServerInteraction.setFailure(function (status, data) {
// revert loading status… // revert loading status…
Alert.hideLoading(); Alert.hideLoading();
TopNav.showViewButtons(); TopNav.showViewButtons();
// show error message // show error message
Alert.showError(Uploader.parseUploadError(status, data, 'get paste data')); Alert.showError(ServerInteraction.parseUploadError(status, data, 'get paste data'));
}); });
Uploader.setSuccess(function (status, data) { ServerInteraction.setSuccess(function (status, data) {
pasteData = data; pasteData = data;
if (typeof callback === 'function') { if (typeof callback === 'function') {
return callback(data); return callback(data);
} }
}); });
Uploader.run(); ServerInteraction.run();
}; };
/** /**
@ -1290,7 +1352,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/ */
me.showStatus = function(message, icon) me.showStatus = function(message, icon)
{ {
console.info('status shown: ', message);
handleNotification(1, $statusMessage, message, icon); handleNotification(1, $statusMessage, message, icon);
}; };
@ -1307,7 +1368,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/ */
me.showError = function(message, icon) me.showError = function(message, icon)
{ {
console.error('error message shown: ', message);
handleNotification(3, $errorMessage, message, icon); handleNotification(3, $errorMessage, message, icon);
}; };
@ -1322,7 +1382,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/ */
me.showRemaining = function(message) me.showRemaining = function(message)
{ {
console.info('remaining message shown: ', message);
handleNotification(1, $remainingTime, message); handleNotification(1, $remainingTime, message);
}; };
@ -1338,10 +1397,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/ */
me.showLoading = function(message, icon) me.showLoading = function(message, icon)
{ {
if (typeof message !== 'undefined' && message !== null) {
console.info('status changed: ', message);
}
// default message text // default message text
if (typeof message === 'undefined') { if (typeof message === 'undefined') {
message = 'Loading…'; message = 'Loading…';
@ -2132,7 +2187,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
me.hide = function() me.hide = function()
{ {
if (!isDisplayed) { if (!isDisplayed) {
console.warn('PasteViewer was called to hide the parsed view, but it is already hidden.'); return;
} }
$plainText.addClass('hidden'); $plainText.addClass('hidden');
@ -3184,7 +3239,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
me.showViewButtons = function() me.showViewButtons = function()
{ {
if (viewButtonsDisplayed) { if (viewButtonsDisplayed) {
console.warn('showViewButtons: view buttons are already displayed');
return; return;
} }
@ -3205,7 +3259,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
me.hideViewButtons = function() me.hideViewButtons = function()
{ {
if (!viewButtonsDisplayed) { if (!viewButtonsDisplayed) {
console.warn('hideViewButtons: view buttons are already hidden');
return; return;
} }
@ -3238,7 +3291,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
me.showCreateButtons = function() me.showCreateButtons = function()
{ {
if (createButtonsDisplayed) { if (createButtonsDisplayed) {
console.warn('showCreateButtons: create buttons are already displayed');
return; return;
} }
@ -3263,7 +3315,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
me.hideCreateButtons = function() me.hideCreateButtons = function()
{ {
if (!createButtonsDisplayed) { if (!createButtonsDisplayed) {
console.warn('hideCreateButtons: create buttons are already hidden');
return; return;
} }
@ -3534,23 +3585,23 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/** /**
* Responsible for AJAX requests, transparently handles encryption * Responsible for AJAX requests, transparently handles encryption
* *
* @name Uploader * @name ServerInteraction
* @class * @class
*/ */
var Uploader = (function () { var ServerInteraction = (function () {
var me = {}; var me = {};
var successFunc = null, var successFunc = null,
failureFunc = null, failureFunc = null,
symmetricKey = null,
url, url,
data, data,
symmetricKey,
password; password;
/** /**
* public variable ('constant') for errors to prevent magic numbers * public variable ('constant') for errors to prevent magic numbers
* *
* @name Uploader.error * @name ServerInteraction.error
* @readonly * @readonly
* @enum {Object} * @enum {Object}
*/ */
@ -3564,7 +3615,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/** /**
* ajaxHeaders to send in AJAX requests * ajaxHeaders to send in AJAX requests
* *
* @name Uploader.ajaxHeaders * @name ServerInteraction.ajaxHeaders
* @private * @private
* @readonly * @readonly
* @enum {Object} * @enum {Object}
@ -3574,40 +3625,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/** /**
* called after successful upload * called after successful upload
* *
* @name Uploader.checkCryptParameters * @name ServerInteraction.success
* @private
* @function
* @throws {string}
*/
function checkCryptParameters()
{
// workaround for this nasty 'bug' in ECMAScript
// see https://stackoverflow.com/questions/18808226/why-is-typeof-null-object
var typeOfKey = typeof symmetricKey;
if (symmetricKey === null) {
typeOfKey = 'null';
}
// in case of missing preparation, throw error
switch (typeOfKey) {
case 'string':
// already set, all right
return;
case 'null':
// needs to be generated auto-generate
symmetricKey = CryptTool.getSymmetricKey();
break;
default:
console.error('current invalid symmetricKey: ', symmetricKey);
throw 'symmetricKey is invalid, probably the module was not prepared';
}
// password is optional
}
/**
* called after successful upload
*
* @name Uploader.success
* @private * @private
* @function * @function
* @param {int} status * @param {int} status
@ -3627,7 +3645,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/** /**
* called after a upload failure * called after a upload failure
* *
* @name Uploader.fail * @name ServerInteraction.fail
* @private * @private
* @function * @function
* @param {int} status - internal code * @param {int} status - internal code
@ -3643,13 +3661,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/** /**
* actually uploads the data * actually uploads the data
* *
* @name Uploader.run * @name ServerInteraction.run
* @function * @function
*/ */
me.run = function() me.run = function()
{ {
$.ajax({ $.ajax({
type: 'POST', type: data ? 'POST' : 'GET',
url: url, url: url,
data: data, data: data,
dataType: 'json', dataType: 'json',
@ -3673,7 +3691,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/** /**
* set success function * set success function
* *
* @name Uploader.setUrl * @name ServerInteraction.setUrl
* @function * @function
* @param {function} newUrl * @param {function} newUrl
*/ */
@ -3684,11 +3702,11 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/** /**
* sets the password to use (first value) and optionally also the * sets the password to use (first value) and optionally also the
* encryption key (not recommend, it is automatically generated). * encryption key (not recommended, it is automatically generated).
* *
* Note: Call this after prepare() as prepare() resets these values. * Note: Call this after prepare() as prepare() resets these values.
* *
* @name Uploader.setCryptValues * @name ServerInteraction.setCryptValues
* @function * @function
* @param {string} newPassword * @param {string} newPassword
* @param {string} newKey - optional * @param {string} newKey - optional
@ -3705,7 +3723,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/** /**
* set success function * set success function
* *
* @name Uploader.setSuccess * @name ServerInteraction.setSuccess
* @function * @function
* @param {function} func * @param {function} func
*/ */
@ -3717,7 +3735,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/** /**
* set failure function * set failure function
* *
* @name Uploader.setFailure * @name ServerInteraction.setFailure
* @function * @function
* @param {function} func * @param {function} func
*/ */
@ -3733,7 +3751,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* previous uploads. Must be called before any other method of this * previous uploads. Must be called before any other method of this
* module. * module.
* *
* @name Uploader.prepare * @name ServerInteraction.prepare
* @function * @function
* @return {object} * @return {object}
*/ */
@ -3757,22 +3775,33 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/** /**
* encrypts and sets the data * encrypts and sets the data
* *
* @name Uploader.setData * @name ServerInteraction.setCipherMessage
* @async * @async
* @function * @function
* @param {string} index * @param {object} cipherMessage
* @param {mixed} element
*/ */
me.setData = async function(index, element) me.setCipherMessage = async function(cipherMessage)
{ {
checkCryptParameters(); if (
data[index] = await CryptTool.cipher(symmetricKey, password, element); symmetricKey === null ||
(typeof symmetricKey === 'string' && symmetricKey === '')
) {
symmetricKey = CryptTool.getSymmetricKey();
}
if (!data.hasOwnProperty('adata')) {
data['adata'] = [];
}
let cipherResult = await CryptTool.cipher(symmetricKey, password, JSON.stringify(cipherMessage), data['adata']);
data['v'] = 2;
data['ct'] = cipherResult[0];
data['adata'] = cipherResult[1];
}; };
/** /**
* set the additional metadata to send unencrypted * set the additional metadata to send unencrypted
* *
* @name Uploader.setUnencryptedData * @name ServerInteraction.setUnencryptedData
* @function * @function
* @param {string} index * @param {string} index
* @param {mixed} element * @param {mixed} element
@ -3783,21 +3812,9 @@ jQuery.PrivateBin = (function($, RawDeflate) {
}; };
/** /**
* set the additional metadata to send unencrypted passed at once * Helper, which parses shows a general error message based on the result of the ServerInteraction
* *
* @name Uploader.setUnencryptedData * @name ServerInteraction.parseUploadError
* @function
* @param {object} newData
*/
me.setUnencryptedBulkData = function(newData)
{
$.extend(data, newData);
};
/**
* Helper, which parses shows a general error message based on the result of the Uploader
*
* @name Uploader.parseUploadError
* @function * @function
* @param {int} status * @param {int} status
* @param {object} data * @param {object} data
@ -3825,24 +3842,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
return errorArray; return errorArray;
}; };
/**
* init Uploader
*
* @name Uploader.init
* @function
*/
me.init = function()
{
// nothing yet
};
return me; return me;
})(); })();
/** /**
* (controller) Responsible for encrypting paste and sending it to server. * (controller) Responsible for encrypting paste and sending it to server.
* *
* Does upload, encryption is done transparently by Uploader. * Does upload, encryption is done transparently by ServerInteraction.
* *
* @name PasteEncrypter * @name PasteEncrypter
* @class * @class
@ -3906,43 +3912,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
}); });
} }
/**
* adds attachments to the Uploader
*
* @name PasteEncrypter.encryptAttachments
* @private
* @function
* @param {function} callback - excuted when action is successful
*/
function encryptAttachments(callback) {
var file = AttachmentViewer.getAttachmentData();
let encryptAttachmentPromise, encryptAttachmentNamePromise;
if (typeof file !== 'undefined' && file !== null) {
var fileName = AttachmentViewer.getFile().name;
// run concurrently to encrypt everything
encryptAttachmentPromise = Uploader.setData('attachment', file);
encryptAttachmentNamePromise = Uploader.setData('attachmentname', fileName);
} else if (AttachmentViewer.hasAttachment()) {
// fall back to cloned part
var attachment = AttachmentViewer.getAttachment();
encryptAttachmentPromise = Uploader.setData('attachment', attachment[0]);
encryptAttachmentNamePromise = Uploader.setData('attachmentname', attachment[1]);
} else {
// if there are no attachments, this is of course still successful
return callback();
}
// TODO: change this callback to also use Promises instead,
// this here just waits
return Promise.all([encryptAttachmentPromise, encryptAttachmentNamePromise]).then(() => {
// run callback
return callback();
});
}
/** /**
* send a reply in a discussion * send a reply in a discussion
* *
@ -3973,20 +3942,20 @@ jQuery.PrivateBin = (function($, RawDeflate) {
return; return;
} }
// prepare Uploader // prepare server interaction
Uploader.prepare(); ServerInteraction.prepare();
Uploader.setCryptParameters(Prompt.getPassword(), Model.getPasteKey()); ServerInteraction.setCryptParameters(Prompt.getPassword(), Model.getPasteKey());
// set success/fail functions // set success/fail functions
Uploader.setSuccess(showUploadedComment); ServerInteraction.setSuccess(showUploadedComment);
Uploader.setFailure(function (status, data) { ServerInteraction.setFailure(function (status, data) {
// revert loading status… // revert loading status…
Alert.hideLoading(); Alert.hideLoading();
TopNav.showViewButtons(); TopNav.showViewButtons();
// …show error message… // …show error message…
Alert.showError( Alert.showError(
Uploader.parseUploadError(status, data, 'post comment') ServerInteraction.parseUploadError(status, data, 'post comment')
); );
// …and reset error handler // …and reset error handler
@ -3994,28 +3963,24 @@ jQuery.PrivateBin = (function($, RawDeflate) {
}); });
// fill it with unencrypted params // fill it with unencrypted params
Uploader.setUnencryptedData('pasteid', Model.getPasteId()); ServerInteraction.setUnencryptedData('pasteid', Model.getPasteId());
if (typeof parentid === 'undefined') { if (typeof parentid === 'undefined') {
// if parent id is not set, this is the top-most comment, so use // if parent id is not set, this is the top-most comment, so use
// paste id as parent, as the root element of the discussion tree // paste id as parent, as the root element of the discussion tree
Uploader.setUnencryptedData('parentid', Model.getPasteId()); ServerInteraction.setUnencryptedData('parentid', Model.getPasteId());
} else { } else {
Uploader.setUnencryptedData('parentid', parentid); ServerInteraction.setUnencryptedData('parentid', parentid);
} }
// start promises to encrypt data… // prepare cypher message
let dataPromises = []; let cipherMessage = {
dataPromises.push(Uploader.setData('data', plainText)); 'comment': plainText
};
if (nickname.length > 0) { if (nickname.length > 0) {
dataPromises.push(Uploader.setData('nickname', nickname)); cipherMessage['nickname'] = nickname;
} }
// …and upload when they are all done await ServerInteraction.setCipherMessage(cipherMessage).catch(Alert.showError);
Promise.all(dataPromises).then(() => {
Uploader.run();
}).catch((e) => {
Alert.showError(e);
});
}; };
/** /**
@ -4049,60 +4014,55 @@ jQuery.PrivateBin = (function($, RawDeflate) {
return; return;
} }
// prepare Uploader // prepare server interaction
Uploader.prepare(); ServerInteraction.prepare();
Uploader.setCryptParameters(TopNav.getPassword()); ServerInteraction.setCryptParameters(TopNav.getPassword());
// set success/fail functions // set success/fail functions
Uploader.setSuccess(showCreatedPaste); ServerInteraction.setSuccess(showCreatedPaste);
Uploader.setFailure(function (status, data) { ServerInteraction.setFailure(function (status, data) {
// revert loading status… // revert loading status…
Alert.hideLoading(); Alert.hideLoading();
TopNav.showCreateButtons(); TopNav.showCreateButtons();
// show error message // show error message
Alert.showError( Alert.showError(
Uploader.parseUploadError(status, data, 'create paste') ServerInteraction.parseUploadError(status, data, 'create paste')
); );
}); });
// fill it with unencrypted submitted options // fill it with unencrypted submitted options
Uploader.setUnencryptedBulkData({ ServerInteraction.setUnencryptedData('adata', [
expire: TopNav.getExpiration(), null, format,
formatter: format, TopNav.getOpenDiscussion() ? 1 : 0,
burnafterreading: TopNav.getBurnAfterReading() ? 1 : 0, TopNav.getBurnAfterReading() ? 1 : 0
opendiscussion: TopNav.getOpenDiscussion() ? 1 : 0 ]);
}); ServerInteraction.setUnencryptedData('meta', {'expire': TopNav.getExpiration()});
// prepare PasteViewer for later preview // prepare PasteViewer for later preview
PasteViewer.setText(plainText); PasteViewer.setText(plainText);
PasteViewer.setFormat(format); PasteViewer.setFormat(format);
// encrypt attachments // prepare cypher message
const encryptAttachmentsPromise = encryptAttachments( let file = AttachmentViewer.getAttachmentData(),
function () { cipherMessage = {
// TODO: remove, is not needed anymore as we use Promises 'paste': plainText
} };
); if (typeof file !== 'undefined' && file !== null) {
cipherMessage['attachment'] = file;
cipherMessage['attachment_name'] = AttachmentViewer.getFile().name;
} else if (AttachmentViewer.hasAttachment()) {
// fall back to cloned part
let attachment = AttachmentViewer.getAttachment();
cipherMessage['attachment'] = attachment[0];
cipherMessage['attachment_name'] = attachment[1];
}
// encrypt plain text // encrypt message
const encryptDataPromise = Uploader.setData('data', plainText); await ServerInteraction.setCipherMessage(cipherMessage).catch(Alert.showError);
await Promise.all([encryptAttachmentsPromise, encryptDataPromise]).catch(Alert.showError);
// send data // send data
Uploader.run(); ServerInteraction.run();
};
/**
* initialize
*
* @name PasteEncrypter.init
* @function
*/
me.init = function()
{
// nothing yet
}; };
return me; return me;
@ -4347,17 +4307,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
}); });
}; };
/**
* initialize
*
* @name PasteDecrypter.init
* @function
*/
me.init = function()
{
// nothing yet
};
return me; return me;
})(); })();
@ -4457,20 +4406,20 @@ jQuery.PrivateBin = (function($, RawDeflate) {
var orgPosition = $(window).scrollTop(); var orgPosition = $(window).scrollTop();
Model.getPasteData(function (data) { Model.getPasteData(function (data) {
Uploader.prepare(); ServerInteraction.prepare();
Uploader.setUrl(Helper.baseUri() + '?' + Model.getPasteId()); ServerInteraction.setUrl(Helper.baseUri() + '?' + Model.getPasteId());
Uploader.setFailure(function (status, data) { ServerInteraction.setFailure(function (status, data) {
// revert loading status… // revert loading status…
Alert.hideLoading(); Alert.hideLoading();
TopNav.showViewButtons(); TopNav.showViewButtons();
// show error message // show error message
Alert.showError( Alert.showError(
Uploader.parseUploadError(status, data, 'refresh display') ServerInteraction.parseUploadError(status, data, 'refresh display')
); );
}); });
Uploader.setSuccess(function (status, data) { ServerInteraction.setSuccess(function (status, data) {
PasteDecrypter.run(data); PasteDecrypter.run(data);
// restore position // restore position
@ -4481,7 +4430,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// password being entered // password being entered
callback(); callback();
}); });
Uploader.run(); ServerInteraction.run();
}, false); // this false is important as it circumvents the cache }, false); // this false is important as it circumvents the cache
} }
@ -4551,14 +4500,11 @@ jQuery.PrivateBin = (function($, RawDeflate) {
AttachmentViewer.init(); AttachmentViewer.init();
DiscussionViewer.init(); DiscussionViewer.init();
Editor.init(); Editor.init();
PasteDecrypter.init();
PasteEncrypter.init();
PasteStatus.init(); PasteStatus.init();
PasteViewer.init(); PasteViewer.init();
Prompt.init(); Prompt.init();
TopNav.init(); TopNav.init();
UiHelper.init(); UiHelper.init();
Uploader.init();
// check whether existing paste needs to be shown // check whether existing paste needs to be shown
try { try {
@ -4602,7 +4548,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
AttachmentViewer: AttachmentViewer, AttachmentViewer: AttachmentViewer,
DiscussionViewer: DiscussionViewer, DiscussionViewer: DiscussionViewer,
TopNav: TopNav, TopNav: TopNav,
Uploader: Uploader, ServerInteraction: ServerInteraction,
PasteEncrypter: PasteEncrypter, PasteEncrypter: PasteEncrypter,
PasteDecrypter: PasteDecrypter, PasteDecrypter: PasteDecrypter,
Controller: Controller Controller: Controller

View File

@ -2,6 +2,11 @@
require('../common'); require('../common');
describe('CryptTool', function () { describe('CryptTool', function () {
afterEach(async function () {
// pause to let async functions conclude
await new Promise(resolve => setTimeout(resolve, 1900));
});
describe('cipher & decipher', function () { describe('cipher & decipher', function () {
this.timeout(30000); this.timeout(30000);
it('can en- and decrypt any message', function () { it('can en- and decrypt any message', function () {
@ -9,24 +14,22 @@ describe('CryptTool', function () {
'string', 'string',
'string', 'string',
'string', 'string',
function (key, password, message) { async function (key, password, message) {
var clean = jsdom(); // pause to let async functions conclude
await new Promise(resolve => setTimeout(resolve, 300));
let clean = jsdom();
window.crypto = new WebCrypto(); window.crypto = new WebCrypto();
message = message.trim(); message = message.trim();
return $.PrivateBin.CryptTool.cipher( let cipherMessage = await $.PrivateBin.CryptTool.cipher(
key, password, message key, password, message, []
).then(function(ciphertext) { ),
$.PrivateBin.CryptTool.decipher( plaintext = await $.PrivateBin.CryptTool.decipher(
key, password, ciphertext key, password, cipherMessage
).then(function(plaintext) { );
clean(); clean();
return message === plaintext; return message === plaintext;
});
});
} }
), ));
// reducing amount of checks as running 100 async ones causes issues for later test scripts
{tests: 3});
}); });
// The below static unit tests are included to ensure deciphering of "classic" // The below static unit tests are included to ensure deciphering of "classic"
@ -35,7 +38,7 @@ describe('CryptTool', function () {
'supports PrivateBin v1 ciphertext (SJCL & browser atob)', 'supports PrivateBin v1 ciphertext (SJCL & browser atob)',
function () { function () {
delete global.Base64; delete global.Base64;
var clean = jsdom(); let clean = jsdom();
window.crypto = new WebCrypto(); window.crypto = new WebCrypto();
// Of course you can easily decipher the following texts, if you like. // Of course you can easily decipher the following texts, if you like.
@ -43,7 +46,7 @@ describe('CryptTool', function () {
return $.PrivateBin.CryptTool.decipher( return $.PrivateBin.CryptTool.decipher(
'6t2qsmLyfXIokNCL+3/yl15rfTUBQvm5SOnFPvNE7Q8=', '6t2qsmLyfXIokNCL+3/yl15rfTUBQvm5SOnFPvNE7Q8=',
// -- "That's amazing. I've got the same combination on my luggage." // -- "That's amazing. I've got the same combination on my luggage."
Array.apply(0, Array(6)).map(function(_,b) { return b + 1; }).join(''), Array.apply(0, Array(6)).map((_,b) => b + 1).join(''),
'{"iv":"4HNFIl7eYbCh6HuShctTIA==","v":1,"iter":10000,"ks"' + '{"iv":"4HNFIl7eYbCh6HuShctTIA==","v":1,"iter":10000,"ks"' +
':256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","sa' + ':256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","sa' +
'lt":"u0lQvePq6L0=","ct":"fGPUVrDyaVr1ZDGb+kqQ3CPEW8x4YKG' + 'lt":"u0lQvePq6L0=","ct":"fGPUVrDyaVr1ZDGb+kqQ3CPEW8x4YKG' +
@ -71,7 +74,7 @@ describe('CryptTool', function () {
'QUxMXI5htsn2rf0HxCFu7Po8DNYLxTS+67hYjDIYWYaEIc8LXWMLyDm9' + 'QUxMXI5htsn2rf0HxCFu7Po8DNYLxTS+67hYjDIYWYaEIc8LXWMLyDm9' +
'C5fARPJ4F2BIWgzgzkNj+dVjusft2XnziamWdbS5u3kuRlVuz5LQj+R5' + 'C5fARPJ4F2BIWgzgzkNj+dVjusft2XnziamWdbS5u3kuRlVuz5LQj+R5' +
'imnqQAincdZTkTT1nYx+DatlOLllCYIHffpI="}' 'imnqQAincdZTkTT1nYx+DatlOLllCYIHffpI="}'
).then(function(paste1) { ).then(function (paste1) {
$.PrivateBin.CryptTool.decipher( $.PrivateBin.CryptTool.decipher(
's9pmKZKOBN7EVvHpTA8jjLFH3Xlz/0l8lB4+ONPACrM=', 's9pmKZKOBN7EVvHpTA8jjLFH3Xlz/0l8lB4+ONPACrM=',
'', // no password '', // no password
@ -97,7 +100,7 @@ describe('CryptTool', function () {
'XhHvixZLcSjX2KQuHmEoWzmJcr3DavdoXZmAurGWLKjzEdJc5dSD/eNr' + 'XhHvixZLcSjX2KQuHmEoWzmJcr3DavdoXZmAurGWLKjzEdJc5dSD/eNr' +
'99gjHX7wphJ6umKMM+fn6PcbYJkhDh2GlJL5COXjXfm/5aj/vuyaRRWZ' + '99gjHX7wphJ6umKMM+fn6PcbYJkhDh2GlJL5COXjXfm/5aj/vuyaRRWZ' +
'MZtmnYpGAtAPg7AUG"}' 'MZtmnYpGAtAPg7AUG"}'
).then(function(paste2) { ).then(function (paste2) {
clean(); clean();
assert.ok( assert.ok(
paste1.includes('securely packed in iron') && paste1.includes('securely packed in iron') &&
@ -120,7 +123,7 @@ describe('CryptTool', function () {
return $.PrivateBin.CryptTool.decipher( return $.PrivateBin.CryptTool.decipher(
'6t2qsmLyfXIokNCL+3/yl15rfTUBQvm5SOnFPvNE7Q8=', '6t2qsmLyfXIokNCL+3/yl15rfTUBQvm5SOnFPvNE7Q8=',
// -- "That's amazing. I've got the same combination on my luggage." // -- "That's amazing. I've got the same combination on my luggage."
Array.apply(0, Array(6)).map(function(_,b) { return b + 1; }).join(''), Array.apply(0, Array(6)).map((_,b) => b + 1).join(''),
'{"iv":"aTnR2qBL1CAmLX8FdWe3VA==","v":1,"iter":10000,"ks"' + '{"iv":"aTnR2qBL1CAmLX8FdWe3VA==","v":1,"iter":10000,"ks"' +
':256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","sa' + ':256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","sa' +
'lt":"u0lQvePq6L0=","ct":"A3nBTvICZtYy6xqbIJE0c8Veored5lM' + 'lt":"u0lQvePq6L0=","ct":"A3nBTvICZtYy6xqbIJE0c8Veored5lM' +
@ -140,7 +143,7 @@ describe('CryptTool', function () {
'7mNNo7xba/YT9KoPDaniqnYqb+q2pX1WNWE7dLS2wfroMAS3kh8P22DA' + '7mNNo7xba/YT9KoPDaniqnYqb+q2pX1WNWE7dLS2wfroMAS3kh8P22DA' +
'V37AeiNoD2PcI6ZcHbRdPa+XRrRcJhSPPW7UQ0z4OvBfjdu/w390QxAx' + 'V37AeiNoD2PcI6ZcHbRdPa+XRrRcJhSPPW7UQ0z4OvBfjdu/w390QxAx' +
'SxvZewoh49fKKB6hTsRnZb4tpHkjlww=="}' 'SxvZewoh49fKKB6hTsRnZb4tpHkjlww=="}'
).then(function(paste1) { ).then(function (paste1) {
$.PrivateBin.CryptTool.decipher( $.PrivateBin.CryptTool.decipher(
's9pmKZKOBN7EVvHpTA8jjLFH3Xlz/0l8lB4+ONPACrM=', 's9pmKZKOBN7EVvHpTA8jjLFH3Xlz/0l8lB4+ONPACrM=',
'', // no password '', // no password
@ -159,7 +162,7 @@ describe('CryptTool', function () {
'7tmfcF73w9dufDFI3LNca2KxzBnWNPYvIZKBwWbq8ncxkb191dP6mjEi' + '7tmfcF73w9dufDFI3LNca2KxzBnWNPYvIZKBwWbq8ncxkb191dP6mjEi' +
'7NnhqVk5A6vIBbu4AC5PZf76l6yep4xsoy/QtdDxCMocCXeAML9MQ9uP' + '7NnhqVk5A6vIBbu4AC5PZf76l6yep4xsoy/QtdDxCMocCXeAML9MQ9uP' +
'QbuspOKrBvMfN5igA1kBqasnxI472KBNXsdZnaDddSVUuvhTcETM="}' 'QbuspOKrBvMfN5igA1kBqasnxI472KBNXsdZnaDddSVUuvhTcETM="}'
).then(function(paste2) { ).then(function (paste2) {
clean(); clean();
delete global.Base64; delete global.Base64;
assert.ok( assert.ok(

View File

@ -71,7 +71,7 @@ if ($MARKDOWN):
endif; endif;
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/purify-1.0.7.js" integrity="sha512-VnKJHLosO8z2ojNvWk9BEKYqnhZyWK9rM90FgZUUEp/PRnUqR5OLLKE0a3BkVmn7YgB7LXRrjHgFHQYKd6DAIA==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/purify-1.0.7.js" integrity="sha512-VnKJHLosO8z2ojNvWk9BEKYqnhZyWK9rM90FgZUUEp/PRnUqR5OLLKE0a3BkVmn7YgB7LXRrjHgFHQYKd6DAIA==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-/hqrAlB/+OWfUg9D/0knhNkmUCzSJNqK2GIU3KBt/vhgfFiKGByOAzFYsyNxINu7c1pEwc/F/ZL5A/iF1rnK0Q==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-RVKB+q9cuKqmERwDv0KDUn1HAeNHCtlWS8Ww9+YeCKW842yyyCEBKw0gR8oS0XU3AXUsKTr3iiUJSJtqrdwm9w==" crossorigin="anonymous"></script>
<!--[if lt IE 10]> <!--[if lt IE 10]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
<![endif]--> <![endif]-->

View File

@ -49,7 +49,7 @@ if ($MARKDOWN):
endif; endif;
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/purify-1.0.7.js" integrity="sha512-VnKJHLosO8z2ojNvWk9BEKYqnhZyWK9rM90FgZUUEp/PRnUqR5OLLKE0a3BkVmn7YgB7LXRrjHgFHQYKd6DAIA==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/purify-1.0.7.js" integrity="sha512-VnKJHLosO8z2ojNvWk9BEKYqnhZyWK9rM90FgZUUEp/PRnUqR5OLLKE0a3BkVmn7YgB7LXRrjHgFHQYKd6DAIA==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-/hqrAlB/+OWfUg9D/0knhNkmUCzSJNqK2GIU3KBt/vhgfFiKGByOAzFYsyNxINu7c1pEwc/F/ZL5A/iF1rnK0Q==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-RVKB+q9cuKqmERwDv0KDUn1HAeNHCtlWS8Ww9+YeCKW842yyyCEBKw0gR8oS0XU3AXUsKTr3iiUJSJtqrdwm9w==" crossorigin="anonymous"></script>
<!--[if lt IE 10]> <!--[if lt IE 10]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
<![endif]--> <![endif]-->