diff --git a/js/privatebin.js b/js/privatebin.js index 93ea2489..bdbafa94 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -924,6 +924,58 @@ jQuery.PrivateBin = (function($, RawDeflate) { }; } + /** + * get PBKDF2 protected credentials for server to validate password + * + * @name CryptTool.getCredentials + * @function + * @param {string} key + * @param {string} password + * @return {string} decrypted message, empty if decryption failed + */ + me.getCredentials = async function(key, password) + { + let keyArray = stringToArraybuffer(key); + if (password.length > 0) { + let passwordArray = stringToArraybuffer(password), + newKeyArray = new Uint8Array(keyArray.length + passwordArray.length); + newKeyArray.set(keyArray, 0); + newKeyArray.set(passwordArray, keyArray.length); + keyArray = newKeyArray; + } + + // import raw key + const importedKey = await window.crypto.subtle.importKey( + 'raw', // only 'raw' is allowed + keyArray.slice(16), + {name: 'PBKDF2'}, // we use PBKDF2 for key derivation + false, // the key may not be exported + ['deriveKey'] // we may only use it for key derivation + ); + + // derive a stronger key for use with AES + const derivedKey = await window.crypto.subtle.deriveKey( + { + name: 'PBKDF2', // we use PBKDF2 for key derivation + salt: keyArray.slice(0, 16), // salt used in HMAC + iterations: 100000, // amount of iterations to apply + hash: {name: 'SHA-256'} // can be "SHA-1", "SHA-256", "SHA-384" or "SHA-512" + }, + importedKey, + { + name: 'AES-GCM', // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC") + length: 256 // can be 128, 192 or 256 + }, + true, // the key can be exported + ['encrypt'] // we want to export it + ); + return btoa( + arraybufferToString( + await window.crypto.subtle.exportKey('raw', derivedKey) + ) + ); + } + /** * compress, then encrypt message with given key and password * diff --git a/js/test/AttachmentViewer.js b/js/test/AttachmentViewer.js index 438b2f89..f59a6ae4 100644 --- a/js/test/AttachmentViewer.js +++ b/js/test/AttachmentViewer.js @@ -4,9 +4,6 @@ var common = require('../common'); describe('AttachmentViewer', function () { describe('setAttachment, showAttachment, removeAttachment, hideAttachment, hideAttachmentPreview, hasAttachment, getAttachment & moveAttachmentTo', function () { this.timeout(30000); - before(function () { - cleanup(); - }); jsc.property( 'displays & hides data as requested', diff --git a/js/test/CryptTool.js b/js/test/CryptTool.js index 80ea5ecb..104eb05e 100644 --- a/js/test/CryptTool.js +++ b/js/test/CryptTool.js @@ -237,19 +237,48 @@ conseq_or_bottom inv (interp (nth_iterate sBody n) (MemElem mem)) }); }); + describe('getCredentials', function () { + it('generates credentials with password', async function () { + const clean = jsdom(); + window.crypto = new WebCrypto(); + // choosen by fair dice roll + const key = atob('EqueAutxlrekNNEvJWB1uaaiwbk/GGpn4++cdk+uDMc='), + // -- "That's amazing. I've got the same combination on my luggage." + password = Array.apply(0, Array(6)).map((_,b) => b + 1).join(''); + const credentials = await $.PrivateBin.CryptTool.getCredentials( + key, password + ); + clean(); + assert.strictEqual(credentials, 'JS8bJWFx1bAPI2LMxfWrw4AQ7cedNVl8UmjUd/pW7Yg='); + }); + + it('generates credentials without password', async function () { + const clean = jsdom(); + window.crypto = new WebCrypto(); + // choosen by fair dice roll + const key = atob('U844LK1y2uUPthTgMvPECwGyQzwScCwkaEI/+qLfQSE='), + password = ''; + const credentials = await $.PrivateBin.CryptTool.getCredentials( + key, password + ); + clean(); + assert.strictEqual(credentials, 'VfAvY7T9rm3K3JKtiOeb+B+rXnE6yZ4bYQTaD9jwjEk='); + }); + }); + describe('getSymmetricKey', function () { this.timeout(30000); - var keys = []; + let keys = []; // the parameter is used to ensure the test is run more then one time jsc.property( 'returns random, non-empty keys', 'integer', function(counter) { - var clean = jsdom(); + const clean = jsdom(); window.crypto = new WebCrypto(); - var key = $.PrivateBin.CryptTool.getSymmetricKey(), - result = (key !== '' && keys.indexOf(key) === -1); + const key = $.PrivateBin.CryptTool.getSymmetricKey(), + result = (key !== '' && keys.indexOf(key) === -1); keys.push(key); clean(); return result; diff --git a/js/test/InitialCheck.js b/js/test/InitialCheck.js index 5b0778cc..90ee251f 100644 --- a/js/test/InitialCheck.js +++ b/js/test/InitialCheck.js @@ -22,7 +22,7 @@ describe('InitialCheck', function () { '' ); $.PrivateBin.Alert.init(); - window.crypto = null; + window.crypto = new WebCrypto(); const result1 = !$.PrivateBin.InitialCheck.init(), result2 = !$('#errormessage').hasClass('hidden'); clean(); @@ -76,7 +76,7 @@ describe('InitialCheck', function () { '' ); $.PrivateBin.Alert.init(); - window.crypto = null; + window.crypto = new WebCrypto(); const result1 = $.PrivateBin.InitialCheck.init(), result2 = isSecureContext === $('#httpnotice').hasClass('hidden'); clean(); diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 711c11ab..fdb7a0ab 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 233b6931..2e98dce4 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -49,7 +49,7 @@ if ($MARKDOWN): endif; ?> - +