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
* @param {string} message
* @param {string} mode
* @param {object} zlib
* @throws {string}
* @return {ArrayBuffer} data
*/
async function compress(message, mode)
async function compress(message, mode, zlib)
{
message = stringToArraybuffer(
utf16To8(message)
);
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 message;
@ -803,16 +807,16 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* @private
* @param {ArrayBuffer} data
* @param {string} mode
* @param {object} zlib
* @throws {string}
* @return {string} message
*/
async function decompress(data, mode)
async function decompress(data, mode, zlib)
{
if (mode === 'zlib' || mode === 'none') {
if (mode === 'zlib') {
let zlib = (await z);
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.')
return '';
throw 'Error decompressing paste, due to missing WebAssembly support.'
}
data = zlib.inflate(
new Uint8Array(data)
@ -962,12 +966,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/
me.cipher = async function(key, password, message, adata)
{
let zlib = (await z);
// AES in Galois Counter Mode, keysize 256 bit,
// authentication tag 128 bit, 10000 iterations in key derivation
const compression = (
typeof (await z) === 'undefined' ?
typeof zlib === 'undefined' ?
'none' : // client lacks support for WASM
$('body').data('compression') || 'zlib'
($('body').data('compression') || 'zlib')
),
spec = [
getRandomBytes(16), // initialization vector
@ -997,7 +1002,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
await window.crypto.subtle.encrypt(
cryptoSettings(JSON.stringify(adata), spec),
await deriveKey(key, password, spec),
await compress(message, compression)
await compress(message, compression, zlib)
).catch(Alert.showError)
)
),
@ -1018,7 +1023,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/
me.decipher = async function(key, password, data)
{
let adataString, spec, cipherMessage;
let adataString, spec, cipherMessage, plaintext;
let zlib = (await z);
if (data instanceof Array) {
// version 2
adataString = JSON.stringify(data[1]);
@ -1045,20 +1051,29 @@ jQuery.PrivateBin = (function($, RawDeflate) {
}
spec[0] = atob(spec[0]);
spec[1] = atob(spec[1]);
if (spec[7] === 'zlib') {
if (typeof zlib === 'undefined') {
throw 'Error decompressing paste, due to missing WebAssembly support.'
}
}
try {
return await decompress(
await window.crypto.subtle.decrypt(
plaintext = await window.crypto.subtle.decrypt(
cryptoSettings(adataString, spec),
await deriveKey(key, password, spec),
stringToArraybuffer(
atob(cipherMessage)
)
).catch(Alert.showError),
spec[7]
);
} catch(err) {
console.error(err);
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 (plaindata.length === 0) {
throw 'failed to decipher data';
return false;
}
return plaindata;
@ -4551,8 +4566,11 @@ jQuery.PrivateBin = (function($, RawDeflate) {
if (password.length === 0) {
throw 'waiting on user to provide a password';
} else {
displayDecryptionError('failed to decipher paste text: Incorrect password?');
throw 'waiting on user to provide correct password';
Alert.hideLoading();
// 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)
*
@ -4714,7 +4711,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
.catch((err) => {
// wait for the user to type in the password,
// 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 &&
'encrypt' in window.crypto.subtle &&
'decrypt' in window.crypto.subtle &&
'Uint8Array' in window &&
'Uint32Array' in window
)) {
return true;
}
if (!(
'WebAssembly' in window &&
'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:
// not checking for async/await, ES6 or Promise support, as most
// browsers introduced these earlier then webassembly and webcrypto:
// https://github.com/PrivateBin/PrivateBin/pull/431#issuecomment-493129359
return false;
@ -4881,7 +4858,9 @@ jQuery.PrivateBin = (function($, RawDeflate) {
}
z = zlib.catch(function () {
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;
}

View file

@ -8,9 +8,6 @@ describe('CryptTool', function () {
await new Promise(resolve => setTimeout(resolve, 1900));
});
// ensure zlib is getting loaded
$.PrivateBin.InitialCheck.init();
this.timeout(30000);
it('can en- and decrypt any message', function () {
jsc.assert(jsc.forall(
@ -21,6 +18,8 @@ describe('CryptTool', function () {
// pause to let async functions conclude
await new Promise(resolve => setTimeout(resolve, 300));
let clean = jsdom();
// ensure zlib is getting loaded
$.PrivateBin.InitialCheck.init();
window.crypto = new WebCrypto();
message = message.trim();
let cipherMessage = await $.PrivateBin.CryptTool.cipher(
@ -182,6 +181,8 @@ describe('CryptTool', function () {
let message = fs.readFileSync('test/compression-sample.txt', 'utf8'),
clean = jsdom();
window.crypto = new WebCrypto();
// ensure zlib is getting loaded
$.PrivateBin.InitialCheck.init();
let cipherMessage = await $.PrivateBin.CryptTool.cipher(
'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))
`;
let clean = jsdom();
// ensure zlib is getting loaded
$.PrivateBin.InitialCheck.init();
window.crypto = new WebCrypto();
let cipherMessage = await $.PrivateBin.CryptTool.cipher(
key, password, message, []

View file

@ -71,7 +71,7 @@ if ($MARKDOWN):
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/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]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;}</style>
<![endif]-->

View file

@ -49,7 +49,7 @@ if ($MARKDOWN):
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/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]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;}</style>
<![endif]-->