Skip to content

Commit

Permalink
new approach to randomness
Browse files Browse the repository at this point in the history
  • Loading branch information
benadida committed Jun 14, 2012
1 parent 40064d1 commit eee08ac
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 8 deletions.
10 changes: 7 additions & 3 deletions lib/jwcrypto.js
Expand Up @@ -9,9 +9,13 @@
var algs = require("./algs/index"), var algs = require("./algs/index"),
utils = require("./utils"), utils = require("./utils"),
delay = utils.delay, delay = utils.delay,
rng = require("./rng"),
libs = require("../libs/minimal"); libs = require("../libs/minimal");


var RNG = new libs.SecureRandom(); var RNG = new rng.RNG();

// start autoseeding now
RNG.autoseed();


function NoSuchAlgorithmException(message) { function NoSuchAlgorithmException(message) {
this.message = message; this.message = message;
Expand Down Expand Up @@ -137,9 +141,9 @@ exports.decrypt = function(encryptedPayload, encryptionAndMACKeys, cb) {


}; };


// rng // entropy here is a string that is expected to be relatively high entropy
exports.addEntropy = function(entropy) { exports.addEntropy = function(entropy) {
// do something! FIXME XXX RNG.addEntropy(entropy);
}; };


exports.assertion = require("./assertion"); exports.assertion = require("./assertion");
Expand Down
80 changes: 80 additions & 0 deletions lib/rng.js
@@ -0,0 +1,80 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
* abstract out RNG depending on client or server.
*
* auto-seeding has to be requested.
* (the seed is automatic, not the decision to auto-seed.)
*
* nextBytes takes a byteArray as input and populates it,
* because that's how the cool kids do it and so we will not bikeshed.
*/

var utils = require("./utils"),
delay = utils.delay,
libs = require("../libs/minimal"),
sjcl = libs.sjcl;

// detect if we have native crypto support
var crypto = null;
try {
crypto = require("crypto");
} catch(e) {}

// proper boolean for whether we have native support
var IS_NATIVE = !!crypto;

function NativeRNG() {
}

NativeRNG.prototype = {
addEntropy: function(seed_in) {
// do nothing, natively we don't care
},
autoseed: function(cb) {
// yay, don't need to do anything
if (cb)
delay(cb)();
},
nextBytes: function(byteArray) {
var randomBytes = crypto.randomBytes(byteArray.length);
for (var i=0; i<byteArray.length; i++)
byteArray[i] = randomBytes[i];
}
};

function BrowserRNG() {
}

BrowserRNG.prototype = {
// WARNING: assumes that there's enough entropy in here to say it's 256
addEntropy: function(seed_in) {
sjcl.random.addEntropy(seed_in, 256);
},
autoseed: function(cb) {
// see if we have window.crypto.getRandomValues
if (window.crypto.getRandomValues) {

This comment has been minimized.

Copy link
@michaelrhanson

michaelrhanson Jun 14, 2012

mightn't this throw if window.crypto is undefined? that may be the case on some browsers.

This comment has been minimized.

Copy link
@benadida

benadida Jun 14, 2012

Author Contributor

cha-ching indeed. Fixing.

// then sjcl already seeded itself
if (cb)
delay(cb)();
} else {
sjcl.random.addEventListener('seeded', function(blarg) {
// no passing of arguments to the callback
if (cb)
cb();
});

// tell sjcl to start collecting some randomness
sjcl.random.startCollectors();
}
},
nextBytes: function(byteArray) {
var randomBytes = sjcl.random.randomWords(byteArray.length);
for (var i=0; i<byteArray.length; i++)
byteArray[i] = randomBytes[i];
}
};

exports.RNG = IS_NATIVE ? NativeRNG : BrowserRNG;

This comment has been minimized.

Copy link
@michaelrhanson

michaelrhanson Jun 14, 2012

no newline makes hulk sad

This comment has been minimized.

Copy link
@benadida

benadida Jun 14, 2012

Author Contributor

i didn't know hulk was so sensitive.

1 change: 0 additions & 1 deletion libs/exports.js
Expand Up @@ -6,7 +6,6 @@


exports.RSAKey = RSAKey; exports.RSAKey = RSAKey;
exports.BigInteger = BigInteger; exports.BigInteger = BigInteger;
exports.SecureRandom = SecureRandom;
exports.sjcl = sjcl; exports.sjcl = sjcl;
exports.hex2b64 = hex2b64; exports.hex2b64 = hex2b64;
exports.b64tohex = b64tohex; exports.b64tohex = b64tohex;
Expand Down
1 change: 0 additions & 1 deletion libs/exports_minimal.js
Expand Up @@ -5,7 +5,6 @@
// //


exports.BigInteger = BigInteger; exports.BigInteger = BigInteger;
exports.SecureRandom = SecureRandom;
exports.sjcl = sjcl; exports.sjcl = sjcl;
exports.hex2b64 = hex2b64; exports.hex2b64 = hex2b64;
exports.b64tohex = b64tohex; exports.b64tohex = b64tohex;
Expand Down
2 changes: 0 additions & 2 deletions libs/minimal_package.txt
Expand Up @@ -6,6 +6,4 @@ external/base64.js
external/jsbn-optimized.js external/jsbn-optimized.js
external/jsbn2-optimized.js external/jsbn2-optimized.js
jsbn-patch.js jsbn-patch.js
external/prng4.js
external/rng.js
exports_minimal.js exports_minimal.js
14 changes: 13 additions & 1 deletion test.html
Expand Up @@ -9,16 +9,28 @@
function runkeygen() { function runkeygen() {
var jwcrypto = require("./lib/jwcrypto"); var jwcrypto = require("./lib/jwcrypto");
var start = new Date(); var start = new Date();
jwcrypto.addEntropy("12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
jwcrypto.generateKeypair({algorithm: "DS", keysize: 256}, function(err, kp) { jwcrypto.generateKeypair({algorithm: "DS", keysize: 256}, function(err, kp) {
var end = new Date(); var end = new Date();
alert(end-start); alert(end-start + "ms");
});
}

function runrandom() {
var rng = require("./lib/rng");
var the_rng = new rng.RNG();
the_rng.autoseed(function() {
var bytes = new Array(20);
the_rng.nextBytes(bytes);
alert(bytes);
}); });
} }


</script> </script>
</head> </head>
<body> <body>
<button onclick="runkeygen();">keygen-DS256</button> <button onclick="runkeygen();">keygen-DS256</button>
<button onclick="runrandom();">run-random</button>
<!-- <!--
<button onclick="runtests();">run tests</button> <button onclick="runtests();">run tests</button>
<button onclick="runperftests();">run perf tests</button> <button onclick="runperftests();">run perf tests</button>
Expand Down
12 changes: 12 additions & 0 deletions test/jwcrypto-test.js
Expand Up @@ -15,6 +15,18 @@ var suite = vows.describe('API tests');
// disable vows (often flakey?) async error behavior // disable vows (often flakey?) async error behavior
suite.options.error = false; suite.options.error = false;


suite.addBatch({
"adding entropy": {
topic: function() {
jwcrypto.addEntropy("foobarbaz");
return null;
},
"works": function() {
assert.ok(true);
}
}
});

testUtils.addBatches(suite, function(alg, keysize) { testUtils.addBatches(suite, function(alg, keysize) {
var keypair; var keypair;
var obj = {foo: "bar"}; var obj = {foo: "bar"};
Expand Down
59 changes: 59 additions & 0 deletions test/rng-test.js
@@ -0,0 +1,59 @@
#!/usr/bin/env node
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const
vows = require('vows'),
assert = require('assert'),
rng = require('../lib/rng');

var suite = vows.describe('RNG tests');

suite.addBatch({
"create rng": {
topic: function() {
return new rng.RNG();
},
"looks good": function(rng) {
assert.isObject(rng);
assert.isFunction(rng.addEntropy);
assert.isFunction(rng.autoseed);
assert.isFunction(rng.nextBytes);
},
"and when we seed": {
topic: function(rng) {
rng.addEntropy("foobar");
return null;
},
"all is well": function() {
assert.ok(true);
}
},
"and when we autoseed": {
topic: function(rng) {
rng.autoseed(this.callback);
},
"eventually returns": function() {
assert.ok(true);
},
"and when we get random bytes": {
topic: function(rng) {
var bytes = [0,0,0,0,0,0,0,0,0,0];
rng.nextBytes(bytes);
return bytes;
},
"contains stuff": function(bytes) {
assert.isArray(bytes);
},
"and that stuff is random'ish": function(bytes) {
// this test is unlikely to fail unless no randomness is getting out
assert.ok(!(bytes[0] == 0 && bytes[1] == 0 && bytes[2] == 0));
}
}
},
}
});
// run or export the suite.
if (process.argv[1] === __filename) suite.run();
else suite.export(module);

2 comments on commit eee08ac

@michaelrhanson
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, will addEntropy() be called in a production environment? If so how?

@benadida
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, addEntropy() will be called to mix in some server-side entropy to ensure that clients that have too little entropy can be upgraded.

Please sign in to comment.