Web worker: simplify random data supply

This commit is contained in:
Thomas Oberndörfer 2014-01-20 09:56:02 +01:00
parent abf0545208
commit 040ccbaf2c
3 changed files with 23 additions and 124 deletions

View File

@ -30,11 +30,8 @@ var crypto = require('../crypto'),
type_keyid = require('../type/keyid.js'),
enums = require('../enums.js');
var INITIAL_SEED = 4096, // random bytes seeded to worker
SEED_REQUEST = 4096, // random bytes seeded after worker request
RSA_FACTOR = 2, // estimated rounds required to find BigInt for p + rounds required to find BigInt for q
DSA_FACTOR = 2 // estimated rounds required in random.getRandomBigIntegerInRange(2, q-1)
ELG_FACTOR = 2; // estimated rounds required in random.getRandomBigIntegerInRange(2, p-2)
var INITIAL_RANDOM_SEED = 50000, // random bytes seeded to worker
RANDOM_SEED_REQUEST = 20000; // random bytes seeded after worker request
/**
* Initializes a new proxy and loads the web worker
@ -44,7 +41,7 @@ var INITIAL_SEED = 4096, // random bytes seeded to worker
function AsyncProxy(path) {
this.worker = new Worker(path || 'openpgp.worker.js');
this.worker.onmessage = this.onMessage.bind(this);
this.seedRandom(INITIAL_SEED);
this.seedRandom(INITIAL_RANDOM_SEED);
// FIFO
this.tasks = [];
}
@ -59,7 +56,7 @@ AsyncProxy.prototype.onMessage = function(event) {
this.tasks.shift()(msg.err ? new Error(msg.err) : null, msg.data);
break;
case 'request-seed':
this.seedRandom(SEED_REQUEST);
this.seedRandom(RANDOM_SEED_REQUEST);
break;
default:
throw new Error('Unknown Worker Event.');
@ -94,53 +91,6 @@ AsyncProxy.prototype.terminate = function() {
this.worker.terminate();
};
/**
* Estimation on how much random bytes are required to process the operation
* @param {String} op 'enc', 'sig' or 'gen'
* @param {Array<module:key~Key>} publicKeys
* @param {Array<module:key~Key>} privateKeys
* @param {Object} options
* @return {Integer} number of bytes required
*/
AsyncProxy.prototype.entropyEstimation = function(op, publicKeys, privateKeys, options) {
var requ = 0; // required entropy in bytes
switch (op) {
case 'enc':
if (!publicKeys) throw new Error('publicKeys required for operation enc');
requ += 32; // max. size of session key
requ += 16; // max. size CFB prefix random
publicKeys.forEach(function(key) {
var subKeyPackets = key.getSubkeyPackets();
for (var i = 0; i < subKeyPackets.length; i++) {
if (enums.write(enums.publicKey, subKeyPackets[i].algorithm) == enums.publicKey.elgamal) {
var keyByteSize = subKeyPackets[i].mpi[0].byteLength();
requ += keyByteSize * ELG_FACTOR; // key byte size for ElGamal keys
break;
}
}
});
break;
case 'sig':
if (!privateKeys) throw new Error('privateKeys required for operation sig');
privateKeys.forEach(function(key) {
if (enums.write(enums.publicKey, key.primaryKey.algorithm) == enums.publicKey.dsa) {
requ += 32 * DSA_FACTOR; // 32 bytes for DSA N value
}
});
break;
case 'gen':
if (!options.numBits) throw new Error('options.numBits required for operation gen');
requ += 8; // salt for S2K;
requ += 16; // CFB initialization vector
requ += (Math.ceil(options.numBits / 8) + 1) * RSA_FACTOR;
requ = requ * 2; // * number of key packets
break;
default:
throw new Error('Unknown operation.');
}
return requ;
};
/**
* Encrypts message text with keys
* @param {Array<module:key~Key>} keys array of keys, used to encrypt the message
@ -148,15 +98,13 @@ AsyncProxy.prototype.entropyEstimation = function(op, publicKeys, privateKeys, o
* @param {Function} callback receives encrypted ASCII armored message
*/
AsyncProxy.prototype.encryptMessage = function(keys, text, callback) {
var estimation = this.entropyEstimation('enc', keys);
keys = keys.map(function(key) {
return key.toPacketlist();
});
this.worker.postMessage({
event: 'encrypt-message',
keys: keys,
text: text,
seed: this.getRandomBuffer(estimation)
text: text
});
this.tasks.push(callback);
};
@ -169,8 +117,6 @@ AsyncProxy.prototype.encryptMessage = function(keys, text, callback) {
* @param {Function} callback receives encrypted ASCII armored message
*/
AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, text, callback) {
var estimation = this.entropyEstimation('enc', publicKeys) +
this.entropyEstimation('sig', null, [privateKey]);
publicKeys = publicKeys.map(function(key) {
return key.toPacketlist();
});
@ -179,8 +125,7 @@ AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, te
event: 'sign-and-encrypt-message',
publicKeys: publicKeys,
privateKey: privateKey,
text: text,
seed: this.getRandomBuffer(estimation)
text: text
});
this.tasks.push(callback);
};
@ -239,15 +184,13 @@ AsyncProxy.prototype.decryptAndVerifyMessage = function(privateKey, publicKeys,
* @param {Function} callback receives ASCII armored message
*/
AsyncProxy.prototype.signClearMessage = function(privateKeys, text, callback) {
var estimation = this.entropyEstimation('sig', null, privateKeys);
privateKeys = privateKeys.map(function(key) {
return key.toPacketlist();
});
this.worker.postMessage({
event: 'sign-clear-message',
privateKeys: privateKeys,
text: text,
seed: this.getRandomBuffer(estimation)
text: text
});
this.tasks.push(callback);
};
@ -294,8 +237,7 @@ AsyncProxy.prototype.generateKeyPair = function(keyType, numBits, userId, passph
keyType: keyType,
numBits: numBits,
userId: userId,
passphrase: passphrase,
seed: this.getRandomBuffer(this.entropyEstimation('gen', null, null, {numBits: numBits}))
passphrase: passphrase
});
this.tasks.push(function(err, data) {
if (data) {

View File

@ -19,8 +19,8 @@ window = {}; // to make UMD bundles work
importScripts('openpgp.js');
var MIN_SIZE_RANDOM_BUFFER = 8192;
var MAX_SIZE_RANDOM_BUFFER = 16384;
var MIN_SIZE_RANDOM_BUFFER = 40000;
var MAX_SIZE_RANDOM_BUFFER = 60000;
window.openpgp.crypto.random.randomBuffer.init(MAX_SIZE_RANDOM_BUFFER);
@ -29,9 +29,6 @@ onmessage = function (event) {
err = null,
msg = event.data,
correct = false;
if (msg.seed) {
window.openpgp.crypto.random.randomBuffer.set(msg.seed);
}
switch (msg.event) {
case 'seed-random':
window.openpgp.crypto.random.randomBuffer.set(msg.buf);

View File

@ -386,6 +386,19 @@ describe('High level API', function() {
});
});
it('Depleted random buffer in worker gives error', function (done) {
var wProxy = new openpgp.AsyncProxy('../dist/openpgp.worker.js');
wProxy.worker = new Worker('../dist/openpgp.worker.js');
wProxy.worker.onmessage = wProxy.onMessage.bind(wProxy);
wProxy.seedRandom(10);
wProxy.encryptMessage([pubKeyRSA], plaintext, function(err, data) {
expect(data).to.not.exist;
expect(err).to.exist;
expect(err).to.eql(new Error('Random number buffer depleted.'));
done();
});
});
});
describe('Key generation', function() {
@ -539,56 +552,3 @@ describe('Random Buffer', function() {
});
});
describe('Entropy Estimation', function() {
var proxy;
before(function() {
proxy = new openpgp.AsyncProxy('../dist/openpgp.worker.js');
expect(proxy).to.exist;
initKeys();
});
it('RSA encrypt', function () {
var oneRSA = proxy.entropyEstimation('enc', [pubKeyRSA]);
// 32 byte session key + 16 byte CFB prefix
expect(oneRSA).to.equal(48);
var twoRSA = proxy.entropyEstimation('enc', [pubKeyRSA, pubKeyRSA]);
// only number of symmetrically encrypted packets relevant (we expect 1)
expect(twoRSA).to.equal(oneRSA);
});
it('RSA sign', function () {
var oneRSA = proxy.entropyEstimation('sig', null, [privKeyRSA]);
// no entropy required for RSA signing
expect(oneRSA).to.equal(0);
});
it('RSA generate', function () {
var oneRSA = proxy.entropyEstimation('gen', null, null, {numBits: 2048});
// 8 salt for S2K
// 16 CFB initialization vector
// 256 byte size of key
// 1 ?
// 2 at least one round required for p and one for and q required
// 2 number of key packets
expect(oneRSA).to.equal((8 + 16 + (256 + 1) * 2) * 2);
});
it('ELG encrypt', function () {
var oneELG = proxy.entropyEstimation('enc', [pubKeyDE]);
// 32 byte session key + 16 byte CFB prefix
// 2 estimated rounds required in random.getRandomBigIntegerInRange(2, p-2)
expect(oneELG).to.equal(48 + (pubKeyDE.subKeys[0].subKey.getBitSize() / 8) * 2);
});
it('DSA sign', function () {
var oneDSA = proxy.entropyEstimation('sig', null, [privKeyDE]);
// 32 bytes for DSA N value
// 2 estimated rounds required in random.getRandomBigIntegerInRange(2, q-1)
expect(oneDSA).to.equal(32 * 2);
});
});