Skip to content
Snippets Groups Projects
Commit 2a342400 authored by Lloyd Hilaiel's avatar Lloyd Hilaiel
Browse files

teach the verifier how to verify assertions issued by primaries

parent 3f0fdf2a
No related branches found
No related tags found
No related merge requests found
......@@ -173,6 +173,15 @@ if (process.env['SHIMMED_PRIMARIES']) {
});
}
exports.getPublicKey = function(domain, cb) {
exports.checkSupport(domain, function(err, rv) {
if (err) return cb(err);
var pubKey = g_cache[domain].publicKey;
if (!pubKey) return cb("can't get public key for " + domain);
cb(null, pubKey);
});
};
// verify an assertion generated to authenticate to browserid
exports.verifyAssertion = function(assertion, cb) {
try {
......@@ -188,10 +197,8 @@ exports.verifyAssertion = function(assertion, cb) {
if (issuer === config.get('hostname')) {
cb("cannot authenticate to browserid with a certificate issued by it.");
} else {
exports.checkSupport(issuer, function(err, rv) {
exports.getPublicKey(issuer, function(err, pubKey) {
if (err) return cb(err);
var pubKey = g_cache[issuer].publicKey;
if (!pubKey) return cb("can't get public key for " + issuer);
next(pubKey);
});
}
......@@ -213,4 +220,4 @@ exports.verifyAssertion = function(assertion, cb) {
cb("can't verify assertion: " + e.toString());
}
}, cb);
};
\ No newline at end of file
};
......@@ -47,7 +47,8 @@ jwcert = require("jwcrypto/jwcert"),
vep = require("jwcrypto/vep"),
config = require("../configuration.js"),
logger = require("../logging.js").logger,
secrets = require('../secrets.js');
secrets = require('../secrets.js'),
primary = require('../primary.js');
try {
const publicKey = secrets.loadPublicKey();
......@@ -126,12 +127,29 @@ function verify(assertion, audience, successCB, errorCB) {
return errorCB("malformed assertion");
}
var ultimateIssuer;
jwcert.JWCert.verifyChain(
bundle.certificates,
new Date(), function(issuer, next) {
// update issuer with each issuer in the chain, so the
// returned issuer will be the last cert in the chain
ultimateIssuer = issuer;
// allow other retrievers for testing
if (issuer === config.get('hostname')) return next(publicKey);
return errorCB("this verifier doesn't respect certs issued from domains other than: " + config.get('hostname'));
// XXX: this network work happening inside a compute process.
// if we have a large number of requests to auth assertions that require
// keyfetch, this could theoretically hurt our throughput. We could
// move the fetch up into the browserid process and pass it into the
// compute process at some point.
// let's go fetch the public key for this host
primary.getPublicKey(issuer, function(err, pubKey) {
if (err) return errorCB(err);
next(pubKey);
});
}, function(pk, principal) {
var tok = new jwt.JWT();
tok.parse(bundle.assertion);
......@@ -144,8 +162,18 @@ function verify(assertion, audience, successCB, errorCB) {
return errorCB("audience mismatch: " + err);
}
// verify that the issuer is the same as the email domain
// NOTE: for "delegation of authority" support we'll need to make this check
// more sophisticated
var domainFromEmail = principal.email.replace(/^.*@/, '');
if (ultimateIssuer != config.get('hostname') && ultimateIssuer !== domainFromEmail)
{
return errorCB("issuer issue '" + ultimateIssuer + "' may not speak for emails from '"
+ domainFromEmail + "'");
}
if (tok.verify(pk)) {
successCB(principal.email, tok.audience, tok.expires, config.get('hostname'));
successCB(principal.email, tok.audience, tok.expires, ultimateIssuer);
} else {
errorCB("verification failure");
}
......
......@@ -49,7 +49,8 @@ jwt = require('jwcrypto/jwt.js'),
vep = require('jwcrypto/vep.js'),
jwcert = require('jwcrypto/jwcert.js'),
http = require('http'),
querystring = require('querystring');
querystring = require('querystring'),
path = require('path');
var suite = vows.describe('verifier');
......@@ -657,10 +658,10 @@ suite.addBatch({
}
});
// now verify that no-one other than browserid is allowed to issue assertions
// (until primary support is implemented)
// now verify that assertions from a primary who does not have browserid support
// will fail to verify
suite.addBatch({
"generating an assertion from a cert signed by some other domain": {
"generating an assertion from a cert signed by a bogus primary": {
topic: function() {
var fakeDomainKeypair = jwk.KeyPair.generate("RS", 64);
var newClientKeypair = jwk.KeyPair.generate("DS", 256);
......@@ -686,7 +687,85 @@ suite.addBatch({
"to return a clear error message": function (r, err) {
var resp = JSON.parse(r.body);
assert.strictEqual(resp.status, 'failure');
assert.strictEqual(resp.reason, "this verifier doesn't respect certs issued from domains other than: 127.0.0.1");
assert.strictEqual(resp.reason, "can't get public key for otherdomain.tld");
}
}
}
});
// now verify that assertions from a primary who does have browserid support
// but has no authority to speak for an email address will fail
suite.addBatch({
"generating an assertion from a cert signed by a real (simulated) primary": {
topic: function() {
var secretKey = jwk.SecretKey.fromSimpleObject(
JSON.parse(require('fs').readFileSync(
path.join(__dirname, '..', 'example', 'primary', 'sample.privatekey'))));
var newClientKeypair = jwk.KeyPair.generate("DS", 256);
expiration = new Date(new Date().getTime() + (1000 * 60 * 60 * 6));
var cert = new jwcert.JWCert("example.domain", expiration, new Date(), newClientKeypair.publicKey,
{email: TEST_EMAIL}).sign(secretKey);
var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
return vep.bundleCertsAndAssertion([cert], tok.sign(newClientKeypair.secretKey));
},
"yields a good looking assertion": function (r, err) {
assert.isString(r);
assert.equal(r.length > 0, true);
},
"will cause the verifier": {
topic: function(assertion) {
wsapi.post('/verify', {
audience: TEST_ORIGIN,
assertion: assertion
}).call(this);
},
"to return a clear error message": function (r, err) {
var resp = JSON.parse(r.body);
assert.strictEqual(resp.status, 'failure');
assert.strictEqual(resp.reason, "issuer issue 'example.domain' may not speak for emails from 'somedomain.com'");
}
}
}
});
// now verify that assertions from a primary who does have browserid support
// and may speak for an email address will succeed
suite.addBatch({
"generating an assertion from a cert signed by a real (simulated) primary": {
topic: function() {
var secretKey = jwk.SecretKey.fromSimpleObject(
JSON.parse(require('fs').readFileSync(
path.join(__dirname, '..', 'example', 'primary', 'sample.privatekey'))));
var newClientKeypair = jwk.KeyPair.generate("DS", 256);
expiration = new Date(new Date().getTime() + (1000 * 60 * 60 * 6));
var cert = new jwcert.JWCert("example.domain", expiration, new Date(), newClientKeypair.publicKey,
{email: "foo@example.domain"}).sign(secretKey);
var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
return vep.bundleCertsAndAssertion([cert], tok.sign(newClientKeypair.secretKey));
},
"yields a good looking assertion": function (r, err) {
assert.isString(r);
assert.equal(r.length > 0, true);
},
"will cause the verifier": {
topic: function(assertion) {
wsapi.post('/verify', {
audience: TEST_ORIGIN,
assertion: assertion
}).call(this);
},
"to return a clear error message": function (r, err) {
var resp = JSON.parse(r.body);
assert.strictEqual(resp.status, 'okay');
assert.strictEqual(resp.issuer, "example.domain");
assert.strictEqual(resp.audience, TEST_ORIGIN);
assert.strictEqual(resp.email, "foo@example.domain");
}
}
}
......
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