making webassembly optional, ensuring retry button works when wrong password is provided

Tested configurations:
- browser with WASM support (Firefox 68.0.2)
  - creates paste with zlib compression, no password
  - creates paste with zlib compression, with password
  - reads paste with zlib compression, no password
  - reads paste with zlib compression, with password + retry button works
  - reads paste without compression, no password
  - reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
  - creates paste without compression, no password, but shows WASM warning
  - creates paste without compression, with password, but shows WASM warning
  - fails to read paste with zlib compression, no password + shows WASM error
  - fails to read paste with zlib compression, with password + shows WASM error
  - reads paste without compression, no password
  - reads paste without compression, with password + retry button works
This commit is contained in:
El RIDO 2019-09-08 08:21:54 +02:00
parent 7a85900b7c
commit 5471757fa7
No known key found for this signature in database
GPG Key ID: 0F5C940A6BD81F92
4 changed files with 56 additions and 74 deletions

View File

@ -780,15 +780,19 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* @private * @private
* @param {string} message * @param {string} message
* @param {string} mode * @param {string} mode
* @param {object} zlib
* @throws {string}
* @return {ArrayBuffer} data * @return {ArrayBuffer} data
*/ */
async function compress(message, mode) async function compress(message, mode, zlib)
{ {
message = stringToArraybuffer( message = stringToArraybuffer(
utf16To8(message) utf16To8(message)
); );
if (mode === 'zlib') { if (mode === 'zlib') {
let zlib = (await z); if (typeof zlib === 'undefined') {
throw 'Error compressing paste, due to missing WebAssembly support.'
}
return zlib.deflate(message).buffer; return zlib.deflate(message).buffer;
} }
return message; return message;
@ -803,16 +807,16 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* @private * @private
* @param {ArrayBuffer} data * @param {ArrayBuffer} data
* @param {string} mode * @param {string} mode
* @param {object} zlib
* @throws {string}
* @return {string} message * @return {string} message
*/ */
async function decompress(data, mode) async function decompress(data, mode, zlib)
{ {
if (mode === 'zlib' || mode === 'none') { if (mode === 'zlib' || mode === 'none') {
if (mode === 'zlib') { if (mode === 'zlib') {
let zlib = (await z);
if (typeof zlib === 'undefined') { if (typeof zlib === 'undefined') {
Alert.showError('Your browser doesn\'t support WebAssembly, used for zlib compression. You can create uncompressed documents, but can\'t read compressed ones.') throw 'Error decompressing paste, due to missing WebAssembly support.'
return '';
} }
data = zlib.inflate( data = zlib.inflate(
new Uint8Array(data) new Uint8Array(data)
@ -962,12 +966,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/ */
me.cipher = async function(key, password, message, adata) me.cipher = async function(key, password, message, adata)
{ {
let zlib = (await z);
// AES in Galois Counter Mode, keysize 256 bit, // AES in Galois Counter Mode, keysize 256 bit,
// authentication tag 128 bit, 10000 iterations in key derivation // authentication tag 128 bit, 10000 iterations in key derivation
const compression = ( const compression = (
typeof (await z) === 'undefined' ? typeof zlib === 'undefined' ?
'none' : // client lacks support for WASM 'none' : // client lacks support for WASM
$('body').data('compression') || 'zlib' ($('body').data('compression') || 'zlib')
), ),
spec = [ spec = [
getRandomBytes(16), // initialization vector getRandomBytes(16), // initialization vector
@ -997,7 +1002,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
await window.crypto.subtle.encrypt( await window.crypto.subtle.encrypt(
cryptoSettings(JSON.stringify(adata), spec), cryptoSettings(JSON.stringify(adata), spec),
await deriveKey(key, password, spec), await deriveKey(key, password, spec),
await compress(message, compression) await compress(message, compression, zlib)
).catch(Alert.showError) ).catch(Alert.showError)
) )
), ),
@ -1018,7 +1023,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/ */
me.decipher = async function(key, password, data) me.decipher = async function(key, password, data)
{ {
let adataString, spec, cipherMessage; let adataString, spec, cipherMessage, plaintext;
let zlib = (await z);
if (data instanceof Array) { if (data instanceof Array) {
// version 2 // version 2
adataString = JSON.stringify(data[1]); adataString = JSON.stringify(data[1]);
@ -1045,20 +1051,29 @@ jQuery.PrivateBin = (function($, RawDeflate) {
} }
spec[0] = atob(spec[0]); spec[0] = atob(spec[0]);
spec[1] = atob(spec[1]); spec[1] = atob(spec[1]);
if (spec[7] === 'zlib') {
if (typeof zlib === 'undefined') {
throw 'Error decompressing paste, due to missing WebAssembly support.'
}
}
try { try {
return await decompress( plaintext = await window.crypto.subtle.decrypt(
await window.crypto.subtle.decrypt( cryptoSettings(adataString, spec),
cryptoSettings(adataString, spec), await deriveKey(key, password, spec),
await deriveKey(key, password, spec), stringToArraybuffer(
stringToArraybuffer( atob(cipherMessage)
atob(cipherMessage) )
)
).catch(Alert.showError),
spec[7]
); );
} catch(err) { } catch(err) {
console.error(err);
return ''; return '';
} }
try {
return await decompress(plaintext, spec[7], zlib);
} catch(err) {
Alert.showError(err);
return err;
}
}; };
/** /**
@ -4522,7 +4537,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// if all tries failed, we can only return an error // if all tries failed, we can only return an error
if (plaindata.length === 0) { if (plaindata.length === 0) {
throw 'failed to decipher data'; return false;
} }
return plaindata; return plaindata;
@ -4551,8 +4566,11 @@ jQuery.PrivateBin = (function($, RawDeflate) {
if (password.length === 0) { if (password.length === 0) {
throw 'waiting on user to provide a password'; throw 'waiting on user to provide a password';
} else { } else {
displayDecryptionError('failed to decipher paste text: Incorrect password?'); Alert.hideLoading();
throw 'waiting on user to provide correct password'; // reset password, so it can be re-entered
Prompt.reset();
TopNav.showRetryButton();
throw 'Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.';
} }
} }
@ -4642,27 +4660,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
}); });
} }
/**
* displays and logs decryption errors
*
* @name PasteDecrypter.displayDecryptionError
* @private
* @function
* @param {string} message
*/
function displayDecryptionError(message)
{
Alert.hideLoading();
// log detailed error, but display generic translation
console.error(message);
Alert.showError('Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.');
// reset password, so it can be re-entered
Prompt.reset();
TopNav.showRetryButton();
}
/** /**
* show decrypted text in the display area, including discussion (if open) * show decrypted text in the display area, including discussion (if open)
* *
@ -4714,7 +4711,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
.catch((err) => { .catch((err) => {
// wait for the user to type in the password, // wait for the user to type in the password,
// then PasteDecrypter.run will be called again // then PasteDecrypter.run will be called again
console.error(err); Alert.showError(err);
}); });
}; };
@ -4818,34 +4815,14 @@ jQuery.PrivateBin = (function($, RawDeflate) {
'subtle' in window.crypto && 'subtle' in window.crypto &&
'encrypt' in window.crypto.subtle && 'encrypt' in window.crypto.subtle &&
'decrypt' in window.crypto.subtle && 'decrypt' in window.crypto.subtle &&
'Uint8Array' in window &&
'Uint32Array' in window 'Uint32Array' in window
)) { )) {
return true; return true;
} }
if (!( // not checking for async/await, ES6 or Promise support, as most
'WebAssembly' in window && // browsers introduced these earlier then webassembly and webcrypto:
'instantiate' in window.WebAssembly
)) {
return true;
}
try {
// [\0, 'a', 's', 'm', (uint_32) 1] - smallest valid wasm module
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
if (
!(
module instanceof WebAssembly.Module &&
new WebAssembly.Instance(module) instanceof WebAssembly.Instance
)
) {
return true;
}
} catch (e) {
return true;
}
// not checking for async/await, ES6, Promise or Uint8Array support,
// as most browsers introduced these earlier then webassembly and webcrypto:
// https://github.com/PrivateBin/PrivateBin/pull/431#issuecomment-493129359 // https://github.com/PrivateBin/PrivateBin/pull/431#issuecomment-493129359
return false; return false;
@ -4881,7 +4858,9 @@ jQuery.PrivateBin = (function($, RawDeflate) {
} }
z = zlib.catch(function () { z = zlib.catch(function () {
Alert.showWarning('Your browser doesn\'t support WebAssembly, used for zlib compression. You can create uncompressed documents, but can\'t read compressed ones.'); if ($('body').data('compression') !== 'none') {
Alert.showWarning('Your browser doesn\'t support WebAssembly, used for zlib compression. You can create uncompressed documents, but can\'t read compressed ones.');
}
}); });
return true; return true;
} }

View File

@ -8,9 +8,6 @@ describe('CryptTool', function () {
await new Promise(resolve => setTimeout(resolve, 1900)); await new Promise(resolve => setTimeout(resolve, 1900));
}); });
// ensure zlib is getting loaded
$.PrivateBin.InitialCheck.init();
this.timeout(30000); this.timeout(30000);
it('can en- and decrypt any message', function () { it('can en- and decrypt any message', function () {
jsc.assert(jsc.forall( jsc.assert(jsc.forall(
@ -21,13 +18,15 @@ describe('CryptTool', function () {
// pause to let async functions conclude // pause to let async functions conclude
await new Promise(resolve => setTimeout(resolve, 300)); await new Promise(resolve => setTimeout(resolve, 300));
let clean = jsdom(); let clean = jsdom();
// ensure zlib is getting loaded
$.PrivateBin.InitialCheck.init();
window.crypto = new WebCrypto(); window.crypto = new WebCrypto();
message = message.trim(); message = message.trim();
let cipherMessage = await $.PrivateBin.CryptTool.cipher( let cipherMessage = await $.PrivateBin.CryptTool.cipher(
key, password, message, [] key, password, message, []
), ),
plaintext = await $.PrivateBin.CryptTool.decipher( plaintext = await $.PrivateBin.CryptTool.decipher(
key, password, cipherMessage key, password, cipherMessage
); );
clean(); clean();
return message === plaintext; return message === plaintext;
@ -182,6 +181,8 @@ describe('CryptTool', function () {
let message = fs.readFileSync('test/compression-sample.txt', 'utf8'), let message = fs.readFileSync('test/compression-sample.txt', 'utf8'),
clean = jsdom(); clean = jsdom();
window.crypto = new WebCrypto(); window.crypto = new WebCrypto();
// ensure zlib is getting loaded
$.PrivateBin.InitialCheck.init();
let cipherMessage = await $.PrivateBin.CryptTool.cipher( let cipherMessage = await $.PrivateBin.CryptTool.cipher(
'foo', 'bar', message, [] 'foo', 'bar', message, []
), ),
@ -225,6 +226,8 @@ isWhile : interp (while expr sBody) (MemElem mem) =
conseq_or_bottom inv (interp (nth_iterate sBody n) (MemElem mem)) conseq_or_bottom inv (interp (nth_iterate sBody n) (MemElem mem))
`; `;
let clean = jsdom(); let clean = jsdom();
// ensure zlib is getting loaded
$.PrivateBin.InitialCheck.init();
window.crypto = new WebCrypto(); window.crypto = new WebCrypto();
let cipherMessage = await $.PrivateBin.CryptTool.cipher( let cipherMessage = await $.PrivateBin.CryptTool.cipher(
key, password, message, [] key, password, message, []

View File

@ -71,7 +71,7 @@ if ($MARKDOWN):
endif; endif;
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/purify-1.0.11.js" integrity="sha512-p7UyJuyBkhMcMgE4mDsgK0Lz70OvetLefua1oXs1OujWv9gOxh4xy8InFux7bZ4/DAZsTmO4rgVwZW9BHKaTaw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/purify-1.0.11.js" integrity="sha512-p7UyJuyBkhMcMgE4mDsgK0Lz70OvetLefua1oXs1OujWv9gOxh4xy8InFux7bZ4/DAZsTmO4rgVwZW9BHKaTaw==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-dsFOXy6/2JHcWi9jwtIIBmAwkRc/2cHDON5YONEo9yFZ7Mt//UFszzk3/kKM77JRDvkHC9gvK/ucgsYT+gyUVw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-tTyV/T13WEwhn9bKY1N0PVu5UFctbDY1qosTTrslVq0R3vPTPjjxSMbUZ3FZBf01rXu39HPV/ibQiD2fOEjYcA==" crossorigin="anonymous"></script>
<!--[if IE]> <!--[if IE]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;}</style> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {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.11.js" integrity="sha512-p7UyJuyBkhMcMgE4mDsgK0Lz70OvetLefua1oXs1OujWv9gOxh4xy8InFux7bZ4/DAZsTmO4rgVwZW9BHKaTaw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/purify-1.0.11.js" integrity="sha512-p7UyJuyBkhMcMgE4mDsgK0Lz70OvetLefua1oXs1OujWv9gOxh4xy8InFux7bZ4/DAZsTmO4rgVwZW9BHKaTaw==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-dsFOXy6/2JHcWi9jwtIIBmAwkRc/2cHDON5YONEo9yFZ7Mt//UFszzk3/kKM77JRDvkHC9gvK/ucgsYT+gyUVw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-tTyV/T13WEwhn9bKY1N0PVu5UFctbDY1qosTTrslVq0R3vPTPjjxSMbUZ3FZBf01rXu39HPV/ibQiD2fOEjYcA==" crossorigin="anonymous"></script>
<!--[if IE]> <!--[if IE]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;}</style> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;}</style>
<![endif]--> <![endif]-->