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'), type_keyid = require('../type/keyid.js'),
enums = require('../enums.js'); enums = require('../enums.js');
var INITIAL_SEED = 4096, // random bytes seeded to worker var INITIAL_RANDOM_SEED = 50000, // random bytes seeded to worker
SEED_REQUEST = 4096, // random bytes seeded after worker request RANDOM_SEED_REQUEST = 20000; // 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)
/** /**
* Initializes a new proxy and loads the web worker * 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) { function AsyncProxy(path) {
this.worker = new Worker(path || 'openpgp.worker.js'); this.worker = new Worker(path || 'openpgp.worker.js');
this.worker.onmessage = this.onMessage.bind(this); this.worker.onmessage = this.onMessage.bind(this);
this.seedRandom(INITIAL_SEED); this.seedRandom(INITIAL_RANDOM_SEED);
// FIFO // FIFO
this.tasks = []; this.tasks = [];
} }
@ -59,7 +56,7 @@ AsyncProxy.prototype.onMessage = function(event) {
this.tasks.shift()(msg.err ? new Error(msg.err) : null, msg.data); this.tasks.shift()(msg.err ? new Error(msg.err) : null, msg.data);
break; break;
case 'request-seed': case 'request-seed':
this.seedRandom(SEED_REQUEST); this.seedRandom(RANDOM_SEED_REQUEST);
break; break;
default: default:
throw new Error('Unknown Worker Event.'); throw new Error('Unknown Worker Event.');
@ -94,53 +91,6 @@ AsyncProxy.prototype.terminate = function() {
this.worker.terminate(); 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 * Encrypts message text with keys
* @param {Array<module:key~Key>} keys array of keys, used to encrypt the message * @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 * @param {Function} callback receives encrypted ASCII armored message
*/ */
AsyncProxy.prototype.encryptMessage = function(keys, text, callback) { AsyncProxy.prototype.encryptMessage = function(keys, text, callback) {
var estimation = this.entropyEstimation('enc', keys);
keys = keys.map(function(key) { keys = keys.map(function(key) {
return key.toPacketlist(); return key.toPacketlist();
}); });
this.worker.postMessage({ this.worker.postMessage({
event: 'encrypt-message', event: 'encrypt-message',
keys: keys, keys: keys,
text: text, text: text
seed: this.getRandomBuffer(estimation)
}); });
this.tasks.push(callback); this.tasks.push(callback);
}; };
@ -169,8 +117,6 @@ AsyncProxy.prototype.encryptMessage = function(keys, text, callback) {
* @param {Function} callback receives encrypted ASCII armored message * @param {Function} callback receives encrypted ASCII armored message
*/ */
AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, text, callback) { AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, text, callback) {
var estimation = this.entropyEstimation('enc', publicKeys) +
this.entropyEstimation('sig', null, [privateKey]);
publicKeys = publicKeys.map(function(key) { publicKeys = publicKeys.map(function(key) {
return key.toPacketlist(); return key.toPacketlist();
}); });
@ -179,8 +125,7 @@ AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, te
event: 'sign-and-encrypt-message', event: 'sign-and-encrypt-message',
publicKeys: publicKeys, publicKeys: publicKeys,
privateKey: privateKey, privateKey: privateKey,
text: text, text: text
seed: this.getRandomBuffer(estimation)
}); });
this.tasks.push(callback); this.tasks.push(callback);
}; };
@ -239,15 +184,13 @@ AsyncProxy.prototype.decryptAndVerifyMessage = function(privateKey, publicKeys,
* @param {Function} callback receives ASCII armored message * @param {Function} callback receives ASCII armored message
*/ */
AsyncProxy.prototype.signClearMessage = function(privateKeys, text, callback) { AsyncProxy.prototype.signClearMessage = function(privateKeys, text, callback) {
var estimation = this.entropyEstimation('sig', null, privateKeys);
privateKeys = privateKeys.map(function(key) { privateKeys = privateKeys.map(function(key) {
return key.toPacketlist(); return key.toPacketlist();
}); });
this.worker.postMessage({ this.worker.postMessage({
event: 'sign-clear-message', event: 'sign-clear-message',
privateKeys: privateKeys, privateKeys: privateKeys,
text: text, text: text
seed: this.getRandomBuffer(estimation)
}); });
this.tasks.push(callback); this.tasks.push(callback);
}; };
@ -294,8 +237,7 @@ AsyncProxy.prototype.generateKeyPair = function(keyType, numBits, userId, passph
keyType: keyType, keyType: keyType,
numBits: numBits, numBits: numBits,
userId: userId, userId: userId,
passphrase: passphrase, passphrase: passphrase
seed: this.getRandomBuffer(this.entropyEstimation('gen', null, null, {numBits: numBits}))
}); });
this.tasks.push(function(err, data) { this.tasks.push(function(err, data) {
if (data) { if (data) {

View File

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