From 0ab06e34ec13b0669c6da8d8ed468af51b50d78d Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 25 Dec 2018 17:34:39 +0100 Subject: [PATCH] initial refactoring for support of version 2 paste format, some cleanup on the side --- js/privatebin.js | 462 +++++++++++++++++++------------------------ js/test/CryptTool.js | 47 ++--- tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 4 files changed, 231 insertions(+), 282 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index 5b3d926..560d6e7 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -701,17 +701,25 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @private * @param {string} key * @param {string} password - * @param {object} object cryptographic message + * @param {array} spec cryptographic specification * @return {CryptoKey} derived key */ - async function deriveKey(key, password, object) + async function deriveKey(key, password, spec) { let keyArray = StrToArr(key); if ((password || '').trim().length > 0) { - keyArray += await window.crypto.subtle.digest( + let passwordBuffer = await window.crypto.subtle.digest( {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 @@ -724,39 +732,40 @@ jQuery.PrivateBin = (function($, RawDeflate) { ); // 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 - salt: StrToArr(atob(object.salt)), // salt used in HMAC - iterations: object.iter, // amount of iterations to apply + salt: StrToArr(spec[1]), // salt used in HMAC + iterations: spec[2], // amount of iterations to apply hash: {name: 'SHA-256'} // can be "SHA-1", "SHA-256", "SHA-384" or "SHA-512" }, 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") - length: object.ks // can be 128, 192 or 256 + 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: spec[3] // can be 128, 192 or 256 }, 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 * @function * @private - * @param {object} object cryptographic message + * @param {string} adata authenticated data + * @param {array} spec cryptographic specification * @return {object} crypto settings */ - function cryptoSettings(object) + function cryptoSettings(adata, spec) { 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") - iv: StrToArr(atob(object.iv)), // the initialization vector you used to encrypt - additionalData: StrToArr(atob(object.adata)), // the addtional data you used during encryption (if any) - tagLength: object.ts // the length of the tag you used to encrypt (if any) + 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(spec[0]), // the initialization vector you used to encrypt + additionalData: StrToArr(adata), // the addtional data you used during encryption (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} password * @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 - const iv = getRandomBytes(16); - let object = { - iv: btoa(iv), - v: 1, - iter: 10000, - ks: 256, - ts: 128, - mode: 'gcm', - adata: '', // if used, base64 encode it with btoa() - cipher: 'aes', - salt: btoa(getRandomBytes(8)) - }; + // AES in Galois Counter Mode, keysize 256 bit, + // authentication tag 128 bit, 10000 iterations in key derivation + const spec = [ + getRandomBytes(16), // initialization vector + getRandomBytes(8), // salt + 10000, // iterations + 256, // key size + 128, // tag size + 'aes', // algorithm + 'gcm', // algorithm mode + 'none' // compression + ], 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 - const encrypted = await window.crypto.subtle.encrypt( - cryptoSettings(object), - await deriveKey(key, password, object), - StrToArr(compress(message)) // compressed plain text to encrypt - ); - object.ct = btoa(ArrToStr(encrypted)); - return JSON.stringify(object); + return [ + btoa( + ArrToStr( + await window.crypto.subtle.encrypt( + cryptoSettings(JSON.stringify(adata), spec), + await deriveKey(key, password, spec), + StrToArr(utob(message)) + ) + ) + ), + adata + ]; }; /** @@ -805,25 +835,57 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @function * @param {string} key * @param {string} password - * @param {string} data - JSON with encrypted data + * @param {string|object} data encrypted message * @return {string} decrypted message, empty if decryption failed */ 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 { - const object = JSON.parse(data); - return decompress( - ArrToStr( - await window.crypto.subtle.decrypt( - cryptoSettings(object), - await deriveKey(key, password, object), - StrToArr(atob(object.ct)) // cipher text to decrypt - ) + plainText = ArrToStr( + await window.crypto.subtle.decrypt( + cryptoSettings(adataString, spec), + await deriveKey(key, password, spec), + StrToArr(atob(cipherMessage)) ) ); } catch(err) { 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 - Uploader.prepare(); - Uploader.setUrl(Helper.baseUri() + '?' + me.getPasteId()); + ServerInteraction.prepare(); + ServerInteraction.setUrl(Helper.baseUri() + '?' + me.getPasteId()); - Uploader.setFailure(function (status, data) { + ServerInteraction.setFailure(function (status, data) { // revert loading status… Alert.hideLoading(); TopNav.showViewButtons(); // 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; if (typeof callback === 'function') { return callback(data); } }); - Uploader.run(); + ServerInteraction.run(); }; /** @@ -1290,7 +1352,6 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ me.showStatus = function(message, icon) { - console.info('status shown: ', message); handleNotification(1, $statusMessage, message, icon); }; @@ -1307,7 +1368,6 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ me.showError = function(message, icon) { - console.error('error message shown: ', message); handleNotification(3, $errorMessage, message, icon); }; @@ -1322,7 +1382,6 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ me.showRemaining = function(message) { - console.info('remaining message shown: ', message); handleNotification(1, $remainingTime, message); }; @@ -1338,10 +1397,6 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ me.showLoading = function(message, icon) { - if (typeof message !== 'undefined' && message !== null) { - console.info('status changed: ', message); - } - // default message text if (typeof message === 'undefined') { message = 'Loading…'; @@ -2132,7 +2187,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { me.hide = function() { if (!isDisplayed) { - console.warn('PasteViewer was called to hide the parsed view, but it is already hidden.'); + return; } $plainText.addClass('hidden'); @@ -3184,7 +3239,6 @@ jQuery.PrivateBin = (function($, RawDeflate) { me.showViewButtons = function() { if (viewButtonsDisplayed) { - console.warn('showViewButtons: view buttons are already displayed'); return; } @@ -3205,7 +3259,6 @@ jQuery.PrivateBin = (function($, RawDeflate) { me.hideViewButtons = function() { if (!viewButtonsDisplayed) { - console.warn('hideViewButtons: view buttons are already hidden'); return; } @@ -3238,7 +3291,6 @@ jQuery.PrivateBin = (function($, RawDeflate) { me.showCreateButtons = function() { if (createButtonsDisplayed) { - console.warn('showCreateButtons: create buttons are already displayed'); return; } @@ -3263,7 +3315,6 @@ jQuery.PrivateBin = (function($, RawDeflate) { me.hideCreateButtons = function() { if (!createButtonsDisplayed) { - console.warn('hideCreateButtons: create buttons are already hidden'); return; } @@ -3534,23 +3585,23 @@ jQuery.PrivateBin = (function($, RawDeflate) { /** * Responsible for AJAX requests, transparently handles encryption… * - * @name Uploader + * @name ServerInteraction * @class */ - var Uploader = (function () { + var ServerInteraction = (function () { var me = {}; var successFunc = null, failureFunc = null, + symmetricKey = null, url, data, - symmetricKey, password; /** * public variable ('constant') for errors to prevent magic numbers * - * @name Uploader.error + * @name ServerInteraction.error * @readonly * @enum {Object} */ @@ -3564,7 +3615,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { /** * ajaxHeaders to send in AJAX requests * - * @name Uploader.ajaxHeaders + * @name ServerInteraction.ajaxHeaders * @private * @readonly * @enum {Object} @@ -3574,40 +3625,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { /** * called after successful upload * - * @name Uploader.checkCryptParameters - * @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 + * @name ServerInteraction.success * @private * @function * @param {int} status @@ -3627,7 +3645,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { /** * called after a upload failure * - * @name Uploader.fail + * @name ServerInteraction.fail * @private * @function * @param {int} status - internal code @@ -3643,13 +3661,13 @@ jQuery.PrivateBin = (function($, RawDeflate) { /** * actually uploads the data * - * @name Uploader.run + * @name ServerInteraction.run * @function */ me.run = function() { $.ajax({ - type: 'POST', + type: data ? 'POST' : 'GET', url: url, data: data, dataType: 'json', @@ -3673,7 +3691,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { /** * set success function * - * @name Uploader.setUrl + * @name ServerInteraction.setUrl * @function * @param {function} newUrl */ @@ -3684,11 +3702,11 @@ jQuery.PrivateBin = (function($, RawDeflate) { /** * 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. * - * @name Uploader.setCryptValues + * @name ServerInteraction.setCryptValues * @function * @param {string} newPassword * @param {string} newKey - optional @@ -3705,7 +3723,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { /** * set success function * - * @name Uploader.setSuccess + * @name ServerInteraction.setSuccess * @function * @param {function} func */ @@ -3717,7 +3735,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { /** * set failure function * - * @name Uploader.setFailure + * @name ServerInteraction.setFailure * @function * @param {function} func */ @@ -3733,7 +3751,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * previous uploads. Must be called before any other method of this * module. * - * @name Uploader.prepare + * @name ServerInteraction.prepare * @function * @return {object} */ @@ -3757,22 +3775,33 @@ jQuery.PrivateBin = (function($, RawDeflate) { /** * encrypts and sets the data * - * @name Uploader.setData + * @name ServerInteraction.setCipherMessage * @async * @function - * @param {string} index - * @param {mixed} element + * @param {object} cipherMessage */ - me.setData = async function(index, element) + me.setCipherMessage = async function(cipherMessage) { - checkCryptParameters(); - data[index] = await CryptTool.cipher(symmetricKey, password, element); + if ( + 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 * - * @name Uploader.setUnencryptedData + * @name ServerInteraction.setUnencryptedData * @function * @param {string} index * @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 - * @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 + * @name ServerInteraction.parseUploadError * @function * @param {int} status * @param {object} data @@ -3825,24 +3842,13 @@ jQuery.PrivateBin = (function($, RawDeflate) { return errorArray; }; - /** - * init Uploader - * - * @name Uploader.init - * @function - */ - me.init = function() - { - // nothing yet - }; - return me; })(); /** * (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 * @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 * @@ -3973,20 +3942,20 @@ jQuery.PrivateBin = (function($, RawDeflate) { return; } - // prepare Uploader - Uploader.prepare(); - Uploader.setCryptParameters(Prompt.getPassword(), Model.getPasteKey()); + // prepare server interaction + ServerInteraction.prepare(); + ServerInteraction.setCryptParameters(Prompt.getPassword(), Model.getPasteKey()); // set success/fail functions - Uploader.setSuccess(showUploadedComment); - Uploader.setFailure(function (status, data) { + ServerInteraction.setSuccess(showUploadedComment); + ServerInteraction.setFailure(function (status, data) { // revert loading status… Alert.hideLoading(); TopNav.showViewButtons(); // …show error message… Alert.showError( - Uploader.parseUploadError(status, data, 'post comment') + ServerInteraction.parseUploadError(status, data, 'post comment') ); // …and reset error handler @@ -3994,28 +3963,24 @@ jQuery.PrivateBin = (function($, RawDeflate) { }); // fill it with unencrypted params - Uploader.setUnencryptedData('pasteid', Model.getPasteId()); + ServerInteraction.setUnencryptedData('pasteid', Model.getPasteId()); if (typeof parentid === 'undefined') { // 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 - Uploader.setUnencryptedData('parentid', Model.getPasteId()); + ServerInteraction.setUnencryptedData('parentid', Model.getPasteId()); } else { - Uploader.setUnencryptedData('parentid', parentid); + ServerInteraction.setUnencryptedData('parentid', parentid); } - // start promises to encrypt data… - let dataPromises = []; - dataPromises.push(Uploader.setData('data', plainText)); + // prepare cypher message + let cipherMessage = { + 'comment': plainText + }; if (nickname.length > 0) { - dataPromises.push(Uploader.setData('nickname', nickname)); + cipherMessage['nickname'] = nickname; } - // …and upload when they are all done - Promise.all(dataPromises).then(() => { - Uploader.run(); - }).catch((e) => { - Alert.showError(e); - }); + await ServerInteraction.setCipherMessage(cipherMessage).catch(Alert.showError); }; /** @@ -4049,60 +4014,55 @@ jQuery.PrivateBin = (function($, RawDeflate) { return; } - // prepare Uploader - Uploader.prepare(); - Uploader.setCryptParameters(TopNav.getPassword()); + // prepare server interaction + ServerInteraction.prepare(); + ServerInteraction.setCryptParameters(TopNav.getPassword()); // set success/fail functions - Uploader.setSuccess(showCreatedPaste); - Uploader.setFailure(function (status, data) { + ServerInteraction.setSuccess(showCreatedPaste); + ServerInteraction.setFailure(function (status, data) { // revert loading status… Alert.hideLoading(); TopNav.showCreateButtons(); // show error message Alert.showError( - Uploader.parseUploadError(status, data, 'create paste') + ServerInteraction.parseUploadError(status, data, 'create paste') ); }); // fill it with unencrypted submitted options - Uploader.setUnencryptedBulkData({ - expire: TopNav.getExpiration(), - formatter: format, - burnafterreading: TopNav.getBurnAfterReading() ? 1 : 0, - opendiscussion: TopNav.getOpenDiscussion() ? 1 : 0 - }); + ServerInteraction.setUnencryptedData('adata', [ + null, format, + TopNav.getOpenDiscussion() ? 1 : 0, + TopNav.getBurnAfterReading() ? 1 : 0 + ]); + ServerInteraction.setUnencryptedData('meta', {'expire': TopNav.getExpiration()}); // prepare PasteViewer for later preview PasteViewer.setText(plainText); PasteViewer.setFormat(format); - // encrypt attachments - const encryptAttachmentsPromise = encryptAttachments( - function () { - // TODO: remove, is not needed anymore as we use Promises - } - ); + // prepare cypher message + let file = AttachmentViewer.getAttachmentData(), + cipherMessage = { + '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 - const encryptDataPromise = Uploader.setData('data', plainText); - - await Promise.all([encryptAttachmentsPromise, encryptDataPromise]).catch(Alert.showError); + // encrypt message + await ServerInteraction.setCipherMessage(cipherMessage).catch(Alert.showError); // send data - Uploader.run(); - }; - - /** - * initialize - * - * @name PasteEncrypter.init - * @function - */ - me.init = function() - { - // nothing yet + ServerInteraction.run(); }; return me; @@ -4347,17 +4307,6 @@ jQuery.PrivateBin = (function($, RawDeflate) { }); }; - /** - * initialize - * - * @name PasteDecrypter.init - * @function - */ - me.init = function() - { - // nothing yet - }; - return me; })(); @@ -4457,20 +4406,20 @@ jQuery.PrivateBin = (function($, RawDeflate) { var orgPosition = $(window).scrollTop(); Model.getPasteData(function (data) { - Uploader.prepare(); - Uploader.setUrl(Helper.baseUri() + '?' + Model.getPasteId()); + ServerInteraction.prepare(); + ServerInteraction.setUrl(Helper.baseUri() + '?' + Model.getPasteId()); - Uploader.setFailure(function (status, data) { + ServerInteraction.setFailure(function (status, data) { // revert loading status… Alert.hideLoading(); TopNav.showViewButtons(); // show error message 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); // restore position @@ -4481,7 +4430,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { // password being entered callback(); }); - Uploader.run(); + ServerInteraction.run(); }, false); // this false is important as it circumvents the cache } @@ -4551,14 +4500,11 @@ jQuery.PrivateBin = (function($, RawDeflate) { AttachmentViewer.init(); DiscussionViewer.init(); Editor.init(); - PasteDecrypter.init(); - PasteEncrypter.init(); PasteStatus.init(); PasteViewer.init(); Prompt.init(); TopNav.init(); UiHelper.init(); - Uploader.init(); // check whether existing paste needs to be shown try { @@ -4602,7 +4548,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { AttachmentViewer: AttachmentViewer, DiscussionViewer: DiscussionViewer, TopNav: TopNav, - Uploader: Uploader, + ServerInteraction: ServerInteraction, PasteEncrypter: PasteEncrypter, PasteDecrypter: PasteDecrypter, Controller: Controller diff --git a/js/test/CryptTool.js b/js/test/CryptTool.js index 4ea7024..c36e7c9 100644 --- a/js/test/CryptTool.js +++ b/js/test/CryptTool.js @@ -2,6 +2,11 @@ require('../common'); describe('CryptTool', function () { + afterEach(async function () { + // pause to let async functions conclude + await new Promise(resolve => setTimeout(resolve, 1900)); + }); + describe('cipher & decipher', function () { this.timeout(30000); it('can en- and decrypt any message', function () { @@ -9,24 +14,22 @@ describe('CryptTool', function () { 'string', 'string', 'string', - function (key, password, message) { - var clean = jsdom(); + async function (key, password, message) { + // pause to let async functions conclude + await new Promise(resolve => setTimeout(resolve, 300)); + let clean = jsdom(); window.crypto = new WebCrypto(); message = message.trim(); - return $.PrivateBin.CryptTool.cipher( - key, password, message - ).then(function(ciphertext) { - $.PrivateBin.CryptTool.decipher( - key, password, ciphertext - ).then(function(plaintext) { - clean(); - return message === plaintext; - }); - }); + let cipherMessage = await $.PrivateBin.CryptTool.cipher( + key, password, message, [] + ), + plaintext = await $.PrivateBin.CryptTool.decipher( + key, password, cipherMessage + ); + clean(); + 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" @@ -35,7 +38,7 @@ describe('CryptTool', function () { 'supports PrivateBin v1 ciphertext (SJCL & browser atob)', function () { delete global.Base64; - var clean = jsdom(); + let clean = jsdom(); window.crypto = new WebCrypto(); // Of course you can easily decipher the following texts, if you like. @@ -43,7 +46,7 @@ describe('CryptTool', function () { return $.PrivateBin.CryptTool.decipher( '6t2qsmLyfXIokNCL+3/yl15rfTUBQvm5SOnFPvNE7Q8=', // -- "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"' + ':256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","sa' + 'lt":"u0lQvePq6L0=","ct":"fGPUVrDyaVr1ZDGb+kqQ3CPEW8x4YKG' + @@ -71,7 +74,7 @@ describe('CryptTool', function () { 'QUxMXI5htsn2rf0HxCFu7Po8DNYLxTS+67hYjDIYWYaEIc8LXWMLyDm9' + 'C5fARPJ4F2BIWgzgzkNj+dVjusft2XnziamWdbS5u3kuRlVuz5LQj+R5' + 'imnqQAincdZTkTT1nYx+DatlOLllCYIHffpI="}' - ).then(function(paste1) { + ).then(function (paste1) { $.PrivateBin.CryptTool.decipher( 's9pmKZKOBN7EVvHpTA8jjLFH3Xlz/0l8lB4+ONPACrM=', '', // no password @@ -97,7 +100,7 @@ describe('CryptTool', function () { 'XhHvixZLcSjX2KQuHmEoWzmJcr3DavdoXZmAurGWLKjzEdJc5dSD/eNr' + '99gjHX7wphJ6umKMM+fn6PcbYJkhDh2GlJL5COXjXfm/5aj/vuyaRRWZ' + 'MZtmnYpGAtAPg7AUG"}' - ).then(function(paste2) { + ).then(function (paste2) { clean(); assert.ok( paste1.includes('securely packed in iron') && @@ -120,7 +123,7 @@ describe('CryptTool', function () { return $.PrivateBin.CryptTool.decipher( '6t2qsmLyfXIokNCL+3/yl15rfTUBQvm5SOnFPvNE7Q8=', // -- "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"' + ':256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","sa' + 'lt":"u0lQvePq6L0=","ct":"A3nBTvICZtYy6xqbIJE0c8Veored5lM' + @@ -140,7 +143,7 @@ describe('CryptTool', function () { '7mNNo7xba/YT9KoPDaniqnYqb+q2pX1WNWE7dLS2wfroMAS3kh8P22DA' + 'V37AeiNoD2PcI6ZcHbRdPa+XRrRcJhSPPW7UQ0z4OvBfjdu/w390QxAx' + 'SxvZewoh49fKKB6hTsRnZb4tpHkjlww=="}' - ).then(function(paste1) { + ).then(function (paste1) { $.PrivateBin.CryptTool.decipher( 's9pmKZKOBN7EVvHpTA8jjLFH3Xlz/0l8lB4+ONPACrM=', '', // no password @@ -159,7 +162,7 @@ describe('CryptTool', function () { '7tmfcF73w9dufDFI3LNca2KxzBnWNPYvIZKBwWbq8ncxkb191dP6mjEi' + '7NnhqVk5A6vIBbu4AC5PZf76l6yep4xsoy/QtdDxCMocCXeAML9MQ9uP' + 'QbuspOKrBvMfN5igA1kBqasnxI472KBNXsdZnaDddSVUuvhTcETM="}' - ).then(function(paste2) { + ).then(function (paste2) { clean(); delete global.Base64; assert.ok( diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 55ebed6..47f9f42 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -71,7 +71,7 @@ if ($MARKDOWN): endif; ?> - + diff --git a/tpl/page.php b/tpl/page.php index d200794..c7333fe 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -49,7 +49,7 @@ if ($MARKDOWN): endif; ?> - +