From 572abadc91363569541a5fcfcf3ea33edf4f101c Mon Sep 17 00:00:00 2001 From: Bart Butler Date: Mon, 5 Mar 2018 17:57:35 -0800 Subject: [PATCH] random number web worker buffer automatic refill --- src/crypto/random.js | 17 +++++++++++------ src/worker/async_proxy.js | 3 --- test/crypto/crypto.js | 18 +++++++++--------- test/crypto/random.js | 10 +++++----- test/general/openpgp.js | 23 ++--------------------- test/worker/async_proxy.js | 15 ++++++--------- 6 files changed, 33 insertions(+), 53 deletions(-) diff --git a/src/crypto/random.js b/src/crypto/random.js index e00af509..e714df31 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -37,7 +37,7 @@ export default { * @param {Integer} length Length in bytes to generate * @return {Uint8Array} Random byte array */ - getRandomBytes: function(length) { + getRandomBytes: async function(length) { const buf = new Uint8Array(length); if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) { window.crypto.getRandomValues(buf); @@ -47,7 +47,7 @@ export default { const bytes = nodeCrypto.randomBytes(buf.length); buf.set(bytes); } else if (this.randomBuffer.buffer) { - this.randomBuffer.get(buf); + await this.randomBuffer.get(buf); } else { throw new Error('No secure random number generator available.'); } @@ -60,7 +60,7 @@ export default { * @param {module:type/mpi} max Upper bound, excluded * @return {module:BN} Random MPI */ - getRandomBN: function(min, max) { + getRandomBN: async function(min, max) { if (max.cmp(min) <= 0) { throw new Error('Illegal parameter value: max <= min'); } @@ -71,7 +71,7 @@ export default { // Using a while loop is necessary to avoid bias introduced by the mod operation. // However, we request 64 extra random bits so that the bias is negligible. // Section B.1.1 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf - const r = new BN(this.getRandomBytes(bytes + 8)); + const r = new BN(await this.getRandomBytes(bytes + 8)); return r.mod(modulus).add(min); }, @@ -121,7 +121,7 @@ RandomBuffer.prototype.set = function(buf) { * Take numbers out of buffer and copy to array * @param {Uint8Array} buf the destination array */ -RandomBuffer.prototype.get = function(buf) { +RandomBuffer.prototype.get = async function(buf) { if (!this.buffer) { throw new Error('RandomBuffer is not initialized'); } @@ -129,7 +129,12 @@ RandomBuffer.prototype.get = function(buf) { throw new Error('Invalid type: buf not an Uint8Array'); } if (this.size < buf.length) { - throw new Error('Random number buffer depleted'); + if (!this.callback) { + throw new Error('Random number buffer depleted'); + } + // Wait for random bytes from main context, then try again + await this.callback(); + return this.get(buf); } for (let i = 0; i < buf.length; i++) { buf[i] = this.buffer[--this.size]; diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index eb404ed3..e5f3f45a 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -19,8 +19,6 @@ import util from '../util.js'; import crypto from '../crypto'; import packet from '../packet'; -const INITIAL_RANDOM_SEED = 50000; // random bytes seeded to worker - /** * Initializes a new proxy and loads the web worker * @constructor @@ -35,7 +33,6 @@ export default function AsyncProxy({ path='openpgp.worker.js', worker, config } this.worker.onerror = e => { throw new Error('Unhandled error in openpgp worker: ' + e.message + ' (' + e.filename + ':' + e.lineno + ')'); }; - this.seedRandom(INITIAL_RANDOM_SEED); if (config) { this.worker.postMessage({ event:'configure', config }); diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index d63c5231..212a38a3 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -278,19 +278,19 @@ describe('API functional testing', function() { }); function testCFB(plaintext, resync) { - symmAlgos.forEach(function(algo) { - const symmKey = crypto.generateSessionKey(algo); - const symmencData = crypto.cfb.encrypt(crypto.getPrefixRandom(algo), algo, util.str_to_Uint8Array(plaintext), symmKey, resync); + symmAlgos.forEach(async function(algo) { + const symmKey = await crypto.generateSessionKey(algo); + const symmencData = crypto.cfb.encrypt(await crypto.getPrefixRandom(algo), algo, util.str_to_Uint8Array(plaintext), symmKey, resync); const text = util.Uint8Array_to_str(crypto.cfb.decrypt(algo, symmKey, symmencData, resync)); expect(text).to.equal(plaintext); }); } function testAESCFB(plaintext) { - symmAlgos.forEach(function(algo) { + symmAlgos.forEach(async function(algo) { if(algo.substr(0,3) === 'aes') { - const symmKey = crypto.generateSessionKey(algo); - const rndm = crypto.getPrefixRandom(algo); + const symmKey = await crypto.generateSessionKey(algo); + const rndm = await crypto.getPrefixRandom(algo); const repeat = new Uint8Array([rndm[rndm.length - 2], rndm[rndm.length - 1]]); const prefix = util.concatUint8Array([rndm, repeat]); @@ -307,9 +307,9 @@ describe('API functional testing', function() { function testAESGCM(plaintext) { symmAlgos.forEach(function(algo) { if(algo.substr(0,3) === 'aes') { - it(algo, function() { - const key = crypto.generateSessionKey(algo); - const iv = crypto.random.getRandomBytes(crypto.gcm.ivLength); + it(algo, async function() { + const key = await crypto.generateSessionKey(algo); + const iv = await crypto.random.getRandomBytes(crypto.gcm.ivLength); return crypto.gcm.encrypt( algo, util.str_to_Uint8Array(plaintext), key, iv diff --git a/test/crypto/random.js b/test/crypto/random.js index 3e53fa12..4c8056d7 100644 --- a/test/crypto/random.js +++ b/test/crypto/random.js @@ -12,9 +12,9 @@ describe('Random Buffer', function() { expect(randomBuffer).to.exist; }); - it('Throw error if not initialized', function () { + it('Throw error if not initialized', async function () { expect(randomBuffer.set.bind(randomBuffer)).to.throw('RandomBuffer is not initialized'); - expect(randomBuffer.get.bind(randomBuffer)).to.throw('RandomBuffer is not initialized'); + await expect(randomBuffer.get(new Uint8Array(1))).to.eventually.be.rejectedWith('RandomBuffer is not initialized'); }); it('Initialization', function () { @@ -56,13 +56,13 @@ describe('Random Buffer', function() { expect(randomBuffer.size).to.equal(1); }); - it('Get Method', function () { + it('Get Method', async function () { randomBuffer.init(5); let buf = new Uint8Array(5); buf[0] = 1; buf[1] = 2; buf[2] = 5; buf[3] = 7; buf[4] = 8; randomBuffer.set(buf); buf = new Uint32Array(2); - expect(randomBuffer.get.bind(randomBuffer, buf)).to.throw('Invalid type: buf not an Uint8Array'); + await expect(randomBuffer.get(buf)).to.eventually.be.rejectedWith('Invalid type: buf not an Uint8Array'); buf = new Uint8Array(2); randomBuffer.get(buf); expect(equal(randomBuffer.buffer, [1,2,5,0,0])).to.be.true; @@ -74,6 +74,6 @@ describe('Random Buffer', function() { expect(buf).to.to.have.property('1', 2); expect(equal(randomBuffer.buffer, [1,0,0,0,0])).to.be.true; expect(randomBuffer.size).to.equal(1); - expect(function() { randomBuffer.get(buf); }).to.throw('Random number buffer depleted'); + await expect(randomBuffer.get(buf)).to.eventually.be.rejectedWith('Random number buffer depleted'); }); }); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 5bbe68ee..fb72aebb 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -896,25 +896,6 @@ describe('OpenPGP.js public api tests', function() { }); }); - it('should encrypt then decrypt', function () { - const encOpt = { - data: plaintext, - publicKeys: publicKey.keys - }; - const decOpt = { - privateKeys: privateKey.keys - }; - return openpgp.encrypt(encOpt).then(function (encrypted) { - expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); - decOpt.message = openpgp.message.readArmored(encrypted.data); - return openpgp.decrypt(decOpt); - }).then(function (decrypted) { - expect(decrypted.data).to.equal(plaintext); - expect(decrypted.signatures).to.exist; - expect(decrypted.signatures.length).to.equal(0); - }); - }); - it('should encrypt then decrypt with multiple private keys', function () { const privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0]; privKeyDE.decrypt(passphrase); @@ -1001,9 +982,9 @@ describe('OpenPGP.js public api tests', function() { }); }); - it('should encrypt using custom session key and decrypt using session key', function () { + it('should encrypt using custom session key and decrypt using session key', async function () { const sessionKey = { - data: openpgp.crypto.generateSessionKey('aes256'), + data: await openpgp.crypto.generateSessionKey('aes256'), algorithm: 'aes256' }; const encOpt = { diff --git a/test/worker/async_proxy.js b/test/worker/async_proxy.js index f7bcd4de..6fb765c6 100644 --- a/test/worker/async_proxy.js +++ b/test/worker/async_proxy.js @@ -47,15 +47,12 @@ tryTests('Async Proxy', tests, { function tests() { - describe('Error handling', function() { - it('Depleted random buffer in worker gives error', function() { - const wProxy = new openpgp.AsyncProxy({ path:'../dist/openpgp.worker.js' }); - wProxy.worker = new Worker('../dist/openpgp.worker.js'); - wProxy.worker.onmessage = wProxy.onMessage.bind(wProxy); - wProxy.seedRandom(10); - return wProxy.delegate('encrypt', { publicKeys:[pubKey], data:plaintext }).catch(function(err) { - expect(err.message).to.match(/Random number buffer depleted/); - }); + describe('Random number pipeline', function() { + it('Random number buffer automatically reseeded', function() { + const worker = new Worker('../dist/openpgp.worker.js'); + const wProxy = new openpgp.AsyncProxy({ path:'../dist/openpgp.worker.js', worker }); + + return wProxy.delegate('encrypt', { publicKeys:[pubKey], data:plaintext }); }); });