Skip to content
Snippets Groups Projects
Commit 766fe215 authored by Austin King's avatar Austin King
Browse files

Implementing delegated authority for primaries. Fixes Issue#1271

Conflicts:

	lib/primary.js
parent 3e39a73e
No related branches found
No related tags found
No related merge requests found
Showing
with 223 additions and 32 deletions
......@@ -19,19 +19,53 @@ config = require("./configuration.js");
const WELL_KNOWN_URL = "/.well-known/browserid";
// Protect from stack overflows and network DDOS attacks
const MAX_AUTHORITY_DELEGATIONS = 6;
const HOSTNAME = urlparse(config.get('public_url')).host;
var g_shim_cache = {};
function parseWellKnownBody(body, domain) {
// This becomes async
function parseWellKnownBody(body, domain, delegates, cb) {
var v = JSON.parse(body);
const want = [ 'public-key', 'authentication', 'provisioning' ];
var got = Object.keys(v);
var bail = false;
got.forEach(function (k) {
if ('authority' === k) {
// Recursion
var dels = Object.keys(delegates);
if (delegates[domain] !== undefined) {
// return to break out of function, but callbacks are actual program flow
bail = true;
return cb("Circular reference in delegating authority " + JSON.stringify(delegates));
}
if (Object.keys(delegates).length > MAX_AUTHORITY_DELEGATIONS) {
bail = true;
return cb("Too many hops while delegating authority " + JSON.stringify(dels));
}
logger.debug(domain,' is delegating to', v[k]);
// recurse into low level get /.well-known/browserid and parse again?
// If everything goes well, finally call our original callback
delegates[domain] = dels.length;
getWellKnown(v[k], delegates, function (err, nbody, ndomain, ndelegates) {
if (err) {
cb(err);
}
parseWellKnownBody(nbody, ndomain, ndelegates, cb);
});
bail = true;;
}
});
if (bail) return;
want.forEach(function(k) {
if (-1 === got.indexOf(k)) throw "missing required key: " + k;
if (-1 === got.indexOf(k)) {
cb("missing required key: " + k);
bail = true;
}
});
if (bail) return;
// Allow SHIMMED_PRIMARIES to change example.com into 127.0.0.1:10005
var url_prefix = 'https://' + domain;
......@@ -49,25 +83,13 @@ function parseWellKnownBody(body, domain) {
urlparse(urls.prov).validate();
// parse the public key
return {
return cb(null, {
publicKey: jwk.PublicKey.fromSimpleObject(v['public-key']),
urls: urls
};
});
}
exports.checkSupport = function(domain, cb) {
if (!cb) throw "missing required callback function";
if (config.get('disable_primary_support')) {
return process.nextTick(function() { cb(null, false); });
}
if (typeof domain !== 'string' || !domain.length) {
return process.nextTick(function() { cb("invalid domain"); });
}
getWellKnown(domain, cb);
};
// Support "shimmed primaries" for local development. That is an environment variable that is any number of
// CSV values of the form:
......@@ -93,9 +115,8 @@ if (process.env['SHIMMED_PRIMARIES']) {
});
}
var getWellKnown = function (domain, cb) {
var getWellKnown = function (domain, delegates, cb) {
function handleResponse(res) {
var msg;
if (res.statusCode !== 200) {
logger.debug(domain + ' is not a browserid primary - non-200 response code to ' + WELL_KNOWN_URL);
return cb(null, false, null);
......@@ -108,22 +129,12 @@ var getWellKnown = function (domain, cb) {
var body = "";
res.on('data', function(chunk) { body += chunk; });
res.on('end', function() {
try {
var r = parseWellKnownBody(body, domain);
logger.info(domain + ' is a valid browserid primary');
return cb(null, r.urls, r.publicKey);
} catch(e) {
msg = domain + ' is a broken browserid primary, malformed dec of support: ' + e.toString();
logger.debug(msg);
return cb(msg);
}
cb(null, body, domain, delegates);
});
};
if (g_shim_cache[domain]) {
var body = g_shim_cache[domain].body,
r = parseWellKnownBody(body, domain);
return cb(null, r.urls, r.publicKey);
return cb(null, g_shim_cache[domain].body, domain, delegates);
}
// now we need to check to see if domain purports to being a primary
......@@ -156,6 +167,48 @@ var getWellKnown = function (domain, cb) {
});
};
exports.checkSupport = function(domain, cb, delegates) {
// Delegates will be populatd via recursion to detect cycles
if (! delegates) {
delegates = {};
}
if (!cb) throw "missing required callback function";
if (config.get('disable_primary_support')) {
return process.nextTick(function() { cb(null, false); });
}
if (typeof domain !== 'string' || !domain.length) {
return process.nextTick(function() { cb("invalid domain"); });
}
getWellKnown(domain, delegates, function (err, body, domain, delegates) {
if (err) {
logger.debug(err);
return cb(err);
} else {
try {
var r = parseWellKnownBody(body, domain, delegates, function (err, r) {
if (err) {
logger.debug(err);
cb(err);
} else {
logger.info(domain + ' is a valid browserid primary');
return cb(null, r.urls, r.publicKey);
}
});
} catch(e) {
var msg = domain + ' is a broken browserid primary, malformed dec of support: ' + e.toString();
logger.debug(msg);
return cb(msg);
}
}
});
};
exports.getPublicKey = function(domain, cb) {
exports.checkSupport(domain, function(err, urls, publicKey) {
if (publicKey === null) {
......@@ -183,6 +236,7 @@ exports.verifyAssertion = function(assertion, cb) {
if (issuer === HOSTNAME) {
cb("cannot authenticate to browserid with a certificate issued by it.");
} else {
exports.getPublicKey(issuer, function(err, pubKey) {
if (err) return cb(err);
next(pubKey);
......
{ "authority": "cycle2.domain" }
{ "authority": "cycle.domain" }
\ No newline at end of file
{ "authority": "delegate1.domain" }
{ "authority": "delegate2.domain" }
{ "authority": "delegate11.domain" }
{ "authority": "delegate3.domain" }
{ "authority": "delegate4.domain" }
{ "authority": "delegate5.domain" }
{ "authority": "delegate6.domain" }
{ "authority": "delegate7.domain" }
{ "authority": "delegate8.domain" }
{ "authority": "delegate9.domain" }
{ "authority": "delegate10.domain" }
{ "authority": "hozed.domain" }
require('./lib/test_env.js');
const
assert = require('assert'),
vows = require('vows'),
path = require('path'),
util = require('util');
const TEST_DOMAIN = 'example.domain',
TEST_DOMAIN_PATH = path.join(__dirname,
'..', 'example', 'primary', '.well-known', 'browserid'),
TEST_ORIGIN = 'http://127.0.0.1:10002',
TEST_DELEGATE_DOMAIN = 'delegate.example.domain',
TEST_DELEGATE_DOMAIN_PATH = path.join(__dirname,
'..', 'example', 'delegated_primary', '.well-known', 'browserid');
// Good examples
process.env['SHIMMED_PRIMARIES'] =
'example.domain|http://127.0.0.1:10005|' + TEST_DOMAIN_PATH;
process.env['SHIMMED_PRIMARIES'] += "," +
'delegate.example.domain|http://127.0.0.1:10005|' + TEST_DELEGATE_DOMAIN_PATH;
// A series of redirects delegate0.domain -> delegate1.domain -> ... delegate11.domain
function mk_delegate(i) {
var f = util.format;
var p = path.join(__dirname, 'data', f('delegate%s.domain', i), '.well-known', 'browserid');
process.env['SHIMMED_PRIMARIES'] += "," +
f("delegate%s.domain|http://127.0.0.1:10005|%s", i, p);
}
for (var i=0; i <= 10; i++) {
mk_delegate(i);
}
// delegates to hozed.domain
process.env['SHIMMED_PRIMARIES'] += "," +
util.format("hozed.domain|http://127.0.0.1:10005|%s", path.join(__dirname, 'data',
'hozed.domain', '.well-known', 'browserid'));
// Next two delegate to each other forming a cycle
process.env['SHIMMED_PRIMARIES'] += "," +
util.format("cycle.domain|http://127.0.0.1:10005|%s", path.join(__dirname, 'data',
'cycle.domain', '.well-known', 'browserid'));
process.env['SHIMMED_PRIMARIES'] += "," +
util.format("cycle2.domain|http://127.0.0.1:10005|%s", path.join(__dirname, 'data',
'cycle2.domain', '.well-known', 'browserid'));
var primary = require('../lib/primary.js');
var suite = vows.describe('delegated-primary');
// DB test look
// Tests related to domains that delegate their authority to another
// primary.
// now let's generate an assertion using this user
suite.addBatch({
"Retrieving a public key is straight forward": {
topic: function() {
return primary.getPublicKey(TEST_DOMAIN, this.callback);
},
"succeeds": function(err, pubKey) {
assert.equal(pubKey.keysize, '256');
assert.equal(pubKey.algorithm, 'RS');
}
}
});
suite.addBatch({
"Retrieving a public key should follow authority delegation": {
topic: function() {
return primary.getPublicKey(TEST_DELEGATE_DOMAIN, this.callback);
},
"succeeds": function(err, pubKey) {
assert.equal(pubKey.keysize, '256');
assert.equal(pubKey.algorithm, 'RS');
}
}
});
suite.addBatch({
"Cycles should be detected": {
topic: function() {
return primary.getPublicKey('cycle.domain', this.callback);
},
"succeeds": function(err, pubKey) {
assert.strictEqual(err,
'Circular reference in delegating authority {"cycle.domain":0,"cycle2.domain":1}');
}
}
});
suite.addBatch({
"We should not follow an infinite series of delegations of authority": {
topic: function() {
return primary.getPublicKey('delegate0.domain', this.callback);
},
"succeeds": function(err, pubKey) {
assert.strictEqual(err,
'Too many hops while delegating authority ["delegate0.domain","delegate1.domain",' +
'"delegate2.domain","delegate3.domain","delegate4.domain","delegate5.domain",' +
'"delegate6.domain"]');
}
}
});
suite.addBatch({
"A domain delegating to itself is hozed...": {
topic: function() {
return primary.getPublicKey('hozed.domain', this.callback);
},
"succeeds": function(err, pubKey) {
assert.strictEqual(err.indexOf('Circular reference in delegating authority '), 0);
}
}
});
// run or export the suite.
if (process.argv[1] === __filename) suite.run();
else suite.export(module);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment