|
|
@@ -44,6 +44,181 @@ describe('CryptTool', function () {
|
|
|
{numRuns: 3});
|
|
|
});
|
|
|
|
|
|
+ // The below static unit tests are included to ensure deciphering of
|
|
|
+ // existing pastes still works, even when the format or encoding of
|
|
|
+ // messages and passwords changes in the future
|
|
|
+ // Of course you can easily decipher the following texts, if you like.
|
|
|
+ // Bonus points for finding their sources and hidden meanings.
|
|
|
+ it(
|
|
|
+ 'supports PrivateBin v2 ciphertext with password',
|
|
|
+ async function () {
|
|
|
+ const clean = globalThis.cleanup();
|
|
|
+ // ensure zlib is getting loaded
|
|
|
+ PrivateBin.Controller.initZlib();
|
|
|
+ Object.defineProperty(window, 'crypto', {
|
|
|
+ value: new WebCrypto(),
|
|
|
+ configurable: true,
|
|
|
+ enumerable: true,
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ global.atob = common.atob;
|
|
|
+ global.btoa = common.btoa;
|
|
|
+
|
|
|
+ const paste = await PrivateBin.CryptTool.decipher(
|
|
|
+ atob('GXPVE/FVL2HbR0eT7R4D8syaBDYwENIgU9N//0d+j74='),
|
|
|
+ // -- "That's amazing. I've got the same combination on my luggage."
|
|
|
+ Array.apply(0, Array(6)).map((_,b) => b + 1).join(''),
|
|
|
+ [
|
|
|
+ 'W9QvTb1da+IXB4Xmayv73zjK816I1egk4adfhL9DNks8ZSbyMV5SOUCj' +
|
|
|
+ 'EPv+Ccy+1nhWFVa9VT4oHi0qZqAECmKoko6oLVdaWFwL1hWMvUdBJfIK' +
|
|
|
+ '13LImrI0l3VGQzXsTKxXgRVHMOn1VqYmE0puo671eUL1g1dx5H1lm2uc' +
|
|
|
+ 'jxrOuLWcHMEF+ZqbAx5xwae1aZ8QptBEDL9MiYov7PQpZduxE/jR+mHA' +
|
|
|
+ 'RzVJV1ZyGYIIiRBa1VpJvFN+0HSkdgYSU3TnzXw+sO+xvNaNv/dqVls1' +
|
|
|
+ 'Lk6mMPninJGxN5ZORw9+eU3sNTzBk0+wrX2lONxUIH3sm5mVZJmdlQ5j' +
|
|
|
+ 'sDC+Pe4lAlhbXfeTeNV6WwvQafmaY+6vrPZWzn5ty3/vrJtCcGYOFxN4' +
|
|
|
+ 'Ra6GwxO3/+JqKppYJPtPvnVYriObPlcEe+6nlXXDS9EM8W2xIdw2f54I' +
|
|
|
+ 'VIA7WHXPj46L6Y/8KpCmlQ5YS7fioWEm3zIbGtzfG8Feu8pT3NVD1wPk' +
|
|
|
+ 'blJcb9eCFEIzzWi9G2/e4HdzRyH4fCV7b1L0Bg50/BpCuaC5+v1eZZ8e' +
|
|
|
+ 'Ku286YB42sKovgKzio9P2WDAxtbq/35nRFbGhC7cvQOzRdibZqFGFIaq' +
|
|
|
+ 'Df0Niz5bvzxKW6eZloBBfm5cxfWdCAX7pTxg/z9Wy/fq3coApwkOS4Ya' +
|
|
|
+ 'NoAOvVZo9g==',
|
|
|
+ [['DLAezyPai/y3Gzo8LmUe5A==', 'G550BaMPE+k=', 100000, 256, 128, 'aes', 'gcm', 'zlib'], 'plaintext', 0, 0]
|
|
|
+ ]
|
|
|
+ );
|
|
|
+ clean();
|
|
|
+ const result = typeof paste === 'string' && paste.includes('securely packed in iron');
|
|
|
+ if (!result) console.log(paste);
|
|
|
+ assert.ok(result);
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ it(
|
|
|
+ 'supports PrivateBin v2 ciphertext no password',
|
|
|
+ async function () {
|
|
|
+ const clean = globalThis.cleanup();
|
|
|
+ // ensure zlib is getting loaded
|
|
|
+ PrivateBin.Controller.initZlib();
|
|
|
+ Object.defineProperty(window, 'crypto', {
|
|
|
+ value: new WebCrypto(),
|
|
|
+ configurable: true,
|
|
|
+ enumerable: true,
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ global.atob = common.atob;
|
|
|
+ global.btoa = common.btoa;
|
|
|
+
|
|
|
+ const paste = await PrivateBin.CryptTool.decipher(
|
|
|
+ atob('RHK8Y2QURMzoDQrbJ5NaAGr3KhIxjepX3fXwXyXQWY0='),
|
|
|
+ '', // no password
|
|
|
+ [
|
|
|
+ 'IB87fuvRS6rBY0RqLxQjG5/Ng1cU1K/2IXQejhIpUWgFI6UWldM/7Vdk' +
|
|
|
+ 'ZxqlmUHIRWIpCrIToODG+5iVHm0RqeW0P98CRHG82Z+d8AAznp4pOyAt' +
|
|
|
+ 'KEa4ifwdoWiSBEbcOBOgggWVUw1nBZt9MHG8iyve+OOH1KPoIv+xVuJV' +
|
|
|
+ 'nBW/+nxQ2EbDcnSY4yixXeoEB1bPjSXLHrd+ifst9SbcsTuHCNng2K/t' +
|
|
|
+ 'mp3CdocycLttfcfPkgTYvYNC2Gd2oCEZP0+0a2dGfT7nzuGRCFbAx+b9' +
|
|
|
+ 'yFNswZHrvlU8CUdrawRSsNN06KZCHkEPUkunjBrdyALLqtn8w2TKA9nf' +
|
|
|
+ '2n+27R5De7dcjOJZbN80PDsAVNOBDCdTuQF+vkUE+254SqQXp2zTAVX5' +
|
|
|
+ '1MjpGgTI/xdSfVdg5vOoqymySPmCV4ZO1sFXPs6AXRg2jhzxhihjxlv8' +
|
|
|
+ '55WY7mSsiU1G3/TBY0Ra+1QzcQ5bSy/ycWEtT72IicM8Hj2w3aF2QwFV' +
|
|
|
+ 'sz5tAyFTZD3swxR5bbxQZJ+LEQ==',
|
|
|
+ ['RSSzQHsYsczisktZn6eesg==', 'Dow4nIXOFgI=', 100000, 256, 128, 'aes', 'gcm', 'zlib']
|
|
|
+ ]
|
|
|
+ );
|
|
|
+ clean();
|
|
|
+ const result = typeof paste === 'string' && paste.includes('Stable, opaque, immortal \u2013 all by dint');
|
|
|
+ if (!result) console.log(paste);
|
|
|
+ assert.ok(result);
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ it(
|
|
|
+ 'supports PrivateBin v2 ciphertext with non-ASCII password and no compression',
|
|
|
+ async function () {
|
|
|
+ const clean = globalThis.cleanup();
|
|
|
+ // ensure zlib is getting loaded
|
|
|
+ PrivateBin.Controller.initZlib();
|
|
|
+ Object.defineProperty(window, 'crypto', {
|
|
|
+ value: new WebCrypto(),
|
|
|
+ configurable: true,
|
|
|
+ enumerable: true,
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ global.atob = common.atob;
|
|
|
+ global.btoa = common.btoa;
|
|
|
+
|
|
|
+ const paste = await PrivateBin.CryptTool.decipher(
|
|
|
+ atob('BgdCS8FLXvEFnQPwkAE93b0urmkaTShdw1Tr5c350Uo='),
|
|
|
+ // key derivation of passwords containing characters above U+007F
|
|
|
+ 'Schl\u00fcssel \u94a5\u5319',
|
|
|
+ [
|
|
|
+ 'T0qlQhwqt1KtFY6r8iqg5gmP3O8yudwx0YPQk7xU4hyIq2SlsXmGGonq' +
|
|
|
+ '6T4sDIJM6i7caSEZYsE/0KeCq0rCw3CZNaUoL9E54sx4ei17m5X9ikUe' +
|
|
|
+ 'feRX3n8M89jbE6vd1qJ13QvbG0mGYhxllfWDnETol0ws2nsfcqVMQJrX' +
|
|
|
+ 'nwlm+3PHH9/sqGChLrbTYMHOcUS1ec4/l5qIFfUBXqbO+U5V5nneZRz8' +
|
|
|
+ '+JuPDk5oSiHTBHV2Jyd0Q3fX37O6bpqEz0Qxl54rLEkfUSRfM7RXCJE2' +
|
|
|
+ 'wcAaxL9vqGcit/QdBnrJrPyTL8Vdoz289dmx+3EJZEtoW1bSEYQ1mov2' +
|
|
|
+ 'c4bv8pEutAEnHf9nbBYZJhWdKlVZkqX5cjM11cPLCuebpQbmw9otVCWX' +
|
|
|
+ 'GAqU8safz+Nu4CkUXoAerX8lrKffJEOLOoGvlJjU2qZexfOjrbxP7XJ3' +
|
|
|
+ 'lbpddDA4OFVDTG9u99m14T4opmYK/iPwbq6PcI1H9fxzLRwyZjNOoaip' +
|
|
|
+ 'b7z6vvOyyl1gwnXN98ZRBys6SChO6tqWATenQgs20UUv6oi+vyBoYhLP' +
|
|
|
+ 'DWFfU4ndf4p2d0BRQldYoXEMl+FgkNMcnS2UR6PhBax/BGDgZlMUljil' +
|
|
|
+ 'rOxcCM5vd5jSZIWs/IV6pMxOAozymCfjMDfgjE2J6ub00V+92uwu6vsV' +
|
|
|
+ 'VlbN1ARFCEp6VX7tN1RNOBTYqlXU6vGbSgXLh7Qng50Pin+/6GKpuHzb' +
|
|
|
+ 'spNmEekIqgzlsNc+/V0BrPe6xvhveVyEANNBgqT5NoRju9ioval08lW0' +
|
|
|
+ 'OfrFZJ5LEk4jZ9TfMEMej1K5efZMUVP94q+D6fRMFwM=',
|
|
|
+ [['RekNgsDiTSbYsXae8EnjTQ==', 'wsZdI484+Cs=', 100000, 256, 128, 'aes', 'gcm', 'none'], 'plaintext', 0, 0]
|
|
|
+ ]
|
|
|
+ );
|
|
|
+ clean();
|
|
|
+ const result = typeof paste === 'string' && paste.includes('Stable, opaque, immortal \u2013 all by dint');
|
|
|
+ if (!result) console.log(paste);
|
|
|
+ assert.ok(result);
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ it(
|
|
|
+ 'supports PrivateBin v2 ciphertext with UTF-8 encoded non-ASCII password',
|
|
|
+ async function () {
|
|
|
+ const clean = globalThis.cleanup();
|
|
|
+ // ensure zlib is getting loaded
|
|
|
+ PrivateBin.Controller.initZlib();
|
|
|
+ Object.defineProperty(window, 'crypto', {
|
|
|
+ value: new WebCrypto(),
|
|
|
+ configurable: true,
|
|
|
+ enumerable: true,
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ global.atob = common.atob;
|
|
|
+ global.btoa = common.btoa;
|
|
|
+
|
|
|
+ const paste = await PrivateBin.CryptTool.decipher(
|
|
|
+ atob('58THB9UKSts178TIRMfG1l+SikJC92wqO+zu6RSbzOk='),
|
|
|
+ // key derivation of passwords using their UTF-8 encoding
|
|
|
+ 'Schl\u00fcssel \u94a5\u5319',
|
|
|
+ [
|
|
|
+ '2LitLhswv6zFfUg4AzEjxd6OHWq8PJRh0ImRpzMUyVz67kuZbTYXSzZt' +
|
|
|
+ '3DLHg/cvT6GpixTZdtHt7z2JK9TXYkoYDcUTuH/xAkgy/k/6w0COOwMp' +
|
|
|
+ 'mUhtYRRTyrhnPEa82QwpVzxZ7zIHRDNLT2J49yLQ44hp564L6zPWQz6R' +
|
|
|
+ 'HGuAf1g3Is/dVRNhRaRPyuOrj/PXeA2/KVkpBbWTY2nPCct6pueLiEA/' +
|
|
|
+ 'pbtI/oOBOo/DQRgLh9MSpZ4a68H6DtWj9/Y6RINm6zn8cit5bOlUYeC5' +
|
|
|
+ 'tpZD2eqpiRZIcwekXcw64Y/dNNQZZ/MD+0ZmWFicBmWlLbuS3WlxeHAH' +
|
|
|
+ 'VLGIoZTydTKopZ6ubmQUbI1Fiz3GUDF3q5e9c7vGUO4TVFIKsCyza/8o' +
|
|
|
+ '2SBfKt66DxTyWTetwcW5ql9L+wfxVf6WFLVkbax/HKvHgozPh6YiGHFJ' +
|
|
|
+ 'DXeNYenlr0R6zQGjHEi+72+gEgDpHtaZSPYVob+ioNO3EsV9WuxqysK+' +
|
|
|
+ 'YEmuw+0cFeF2l9ki69AjbtzNjrFgAsX1QR9an+BtUXFeMekwgkx0NkUd' +
|
|
|
+ 'Sla2eOcoIQSR8HFkOF4tCQPxp4Pgd+UFAMYDkv55sCzPxHm3JXbVCzhw' +
|
|
|
+ '7N5zmeEI3wJiqHzLzcRK/JqnaPUinqwcv+skickiAAGUKRZwUmHirCyc' +
|
|
|
+ 'WbIqDxlddQ==',
|
|
|
+ ['R9xhVxI3CtY6hp45e1ad0w==', 'LqC0jpwAdps=', 100000, 256, 128, 'aes', 'gcm', 'zlib']
|
|
|
+ ]
|
|
|
+ );
|
|
|
+ clean();
|
|
|
+ const result = typeof paste === 'string' && paste.includes('securely packed in iron');
|
|
|
+ if (!result) console.log(paste);
|
|
|
+ assert.ok(result);
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
it('does not truncate messages', async function () {
|
|
|
const message = fs.readFileSync('test/compression-sample.txt', 'ascii').trim(),
|
|
|
clean = globalThis.cleanup();
|
|
|
@@ -151,6 +326,73 @@ conseq_or_bottom inv (interp (nth_iterate sBody n) (MemElem mem))
|
|
|
),
|
|
|
{numRuns: 3});
|
|
|
});
|
|
|
+
|
|
|
+ it('can en- and decrypt any unicode message and password', async function () {
|
|
|
+ await fc.assert(fc.asyncProperty(
|
|
|
+ fc.string({unit: 'binary', minLength: 1}),
|
|
|
+ fc.string({unit: 'binary'}),
|
|
|
+ async function (message, password) {
|
|
|
+ const clean = globalThis.cleanup();
|
|
|
+ // ensure zlib is getting loaded
|
|
|
+ PrivateBin.Controller.initZlib();
|
|
|
+ Object.defineProperty(window, 'crypto', {
|
|
|
+ value: new WebCrypto(),
|
|
|
+ configurable: true,
|
|
|
+ enumerable: true,
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ global.atob = common.atob;
|
|
|
+ global.btoa = common.btoa;
|
|
|
+ const key = PrivateBin.CryptTool.getSymmetricKey(),
|
|
|
+ cipherMessage = await PrivateBin.CryptTool.cipher(
|
|
|
+ key, password, message, []
|
|
|
+ ),
|
|
|
+ plaintext = await PrivateBin.CryptTool.decipher(
|
|
|
+ key, password, cipherMessage
|
|
|
+ );
|
|
|
+ clean();
|
|
|
+ const result = (message === plaintext);
|
|
|
+ if (!result) console.log(plaintext, cipherMessage);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ ),
|
|
|
+ {numRuns: 3});
|
|
|
+ });
|
|
|
+
|
|
|
+ it('does not accept passwords that only share the low byte of each character', async function () {
|
|
|
+ const passwordPairs = [
|
|
|
+ ['\u013c', '<'], // U+013C vs <
|
|
|
+ ['\u4e2d\u6587', '-\u0087'], // U+4E2D U+6587 vs 0x2D 0x87
|
|
|
+ ['\u20ac', '\u00ac'] // U+20AC vs 0xAC
|
|
|
+ ];
|
|
|
+ for (const [password, lowBytePassword] of passwordPairs) {
|
|
|
+ const clean = globalThis.cleanup();
|
|
|
+ // ensure zlib is getting loaded
|
|
|
+ PrivateBin.Controller.initZlib();
|
|
|
+ Object.defineProperty(window, 'crypto', {
|
|
|
+ value: new WebCrypto(),
|
|
|
+ configurable: true,
|
|
|
+ enumerable: true,
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ global.atob = common.atob;
|
|
|
+ global.btoa = common.btoa;
|
|
|
+ const key = PrivateBin.CryptTool.getSymmetricKey(),
|
|
|
+ cipherMessage = await PrivateBin.CryptTool.cipher(
|
|
|
+ key, password, 'secret', []
|
|
|
+ ),
|
|
|
+ plaintext = await PrivateBin.CryptTool.decipher(
|
|
|
+ key, lowBytePassword, cipherMessage
|
|
|
+ );
|
|
|
+ clean();
|
|
|
+ assert.strictEqual(
|
|
|
+ plaintext, '',
|
|
|
+ 'password ' + JSON.stringify(lowBytePassword) +
|
|
|
+ ' must not decrypt a document encrypted with ' +
|
|
|
+ JSON.stringify(password)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ });
|
|
|
});
|
|
|
|
|
|
describe('getSymmetricKey', function () {
|