random number web worker buffer automatic refill

This commit is contained in:
Bart Butler 2018-03-05 17:57:35 -08:00
parent 433ae5cce7
commit 572abadc91
6 changed files with 33 additions and 53 deletions

View File

@ -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];

View File

@ -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 });

View File

@ -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

View File

@ -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');
});
});

View File

@ -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 = {

View File

@ -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 });
});
});