Allow IdPs to publish multiple public keys.

This allows the IdP support document to include a 'keys'
property containing a list of public keys, any of which
could be used to produce a valid signature.  IdPs might like
to use this during e.g. key rotation events.
This commit is contained in:
Ryan Kelly 2016-02-08 15:50:57 +11:00
Родитель 0760ddcb03
Коммит 30dbded9c4
7 изменённых файлов: 99 добавлений и 15 удалений

Просмотреть файл

@ -4,8 +4,9 @@ env:
global:
- TMPDIR=/tmp
before_install:
- sudo apt-get install libgmp3-dev
addons:
apt_packages:
- libgmp-dev
node_js:
- "0.10"

Просмотреть файл

@ -63,6 +63,9 @@ browserid.lookup(function(err, details) {
if (argv.v) console.log("\n");
// convert publicKey to displayable object
details.publicKey = details.publicKey.toSimpleObject();
for (var i=0; i<details.publicKeys.length; i++) {
details.publicKeys[i] = details.publicKeys[i].toSimpleObject();
}
console.log(details.authoritativeDomain.info, "is authoritative for", '@' + principalDomain.info, "email addresses:", JSON.stringify(details, null, 2).data);
}
});

Просмотреть файл

@ -235,7 +235,10 @@ function lookup(emitter, args, currentDomain, principalDomain, cb, delegationCha
var url_prefix = 'https://' + currentDomain;
var details = {
// For b/w compat export both a single "current key"
// as well as a list of all acceptable keys.
publicKey: supportDoc.publicKey,
publicKeys: supportDoc.publicKeys,
delegationChain: delegationChain,
authoritativeDomain: delegationChain[delegationChain.length - 1],
urls: {

Просмотреть файл

@ -134,7 +134,7 @@ function verify(browserid, args, cb) {
dbug.error(err);
return cb(err);
}
next(null, details.publicKey);
next(null, details.publicKeys);
});
}, function(err, certParamsArray, payload, assertionParams) {
if (err) {

Просмотреть файл

@ -19,7 +19,8 @@ function validateUrlPath(path) {
// * if type is "delegation", also:
// * authority - the domain authority is delegated to
// * if type is "supported":
// * publicKey - a parsed representation of the public key
// * publicKeys - a parsed representation of its list of published public keys
// * publicKey - a parsed representation of its current public key
// * paths.authentication - the path to the 'authentication' html
// * paths.provisioning - the path to the 'provisioning' html
module.exports = function(doc) {
@ -35,7 +36,7 @@ module.exports = function(doc) {
// there are three main types of support documents
// 1. "supported" - declares the domain is a browserid authority,
// contains public-key, authentication, and provisioning
// contains public keys, authentication, and provisioning
// 2. "delegation" - declares the domain allows a different domain
// to be authoritative for it.
// 3. "disable" - domain declares explicitly that it wants a secondary
@ -71,6 +72,7 @@ module.exports = function(doc) {
var parsed = {
type: "supported",
paths: {},
publicKeys: [],
publicKey: null
};
@ -83,15 +85,41 @@ module.exports = function(doc) {
}
});
if (!doc['public-key']) {
throw new Error("support document missing required 'public-key'");
// For backwards-compat reasons, the support document can contain
// one or both of:
// * a single current public key in 'public-key'
// * a list of acceptable public keys in 'keys'
if (doc['public-key']) {
try {
parsed.publicKey = jwcrypto.loadPublicKeyFromObject(doc['public-key']);
} catch(e) {
throw new Error("mal-formed public key in support doc: " + e.toString());
}
parsed.publicKeys.push(parsed.publicKey);
}
// can we parse that key?
try {
parsed.publicKey = jwcrypto.loadPublicKeyFromObject(doc['public-key']);
} catch(e) {
throw new Error("mal-formed public key in support doc: " + e.toString());
if (doc.keys) {
if (!Array.isArray(doc.keys)) {
throw new Error("mal-formed list of public keys in support doc");
}
doc.keys.forEach(function(key) {
try {
// Ensure only keys meant for signing are included in the list.
if (!key.use || key.use === 'sig') {
parsed.publicKeys.push(jwcrypto.loadPublicKeyFromObject(key));
}
} catch(e) {
throw new Error("mal-formed public key in support doc: " + e.toString());
}
});
}
if (parsed.publicKeys.length === 0) {
throw new Error("support document missing required property 'keys' and/or 'public-key'");
}
if (!parsed.publicKey) {
parsed.publicKey = parsed.publicKeys[0];
}
// success!

Просмотреть файл

@ -11,7 +11,7 @@
},
"main": "lib/browserid-local-verify",
"dependencies": {
"browserid-crypto": "0.7.0",
"browserid-crypto": "git+https://github.com/mozilla/browserid-crypto#support-multiple-root-pubkeys",
"async": "0.2.9",
"dbug": "0.4.1",
"urlparse": "0.0.1",
@ -25,7 +25,7 @@
"jshint": "2.3.0",
"walk": "2.2.1",
"temp": "0.5.1",
"ass": "0.0.4"
"ass": "git://github.com/jrgm/ass.git#5be99ee7abc9fcf63f9ebcc37b151b9c822146d1"
},
"scripts": {
"test": "mocha -R spec tests/*.js"

Просмотреть файл

@ -18,7 +18,7 @@ describe('.well-known lookup, malformed', function() {
it('should handle bogus public key', function(done) {
var x = idp.wellKnown();
x['public-key'].n += "bogus";
delete x['public-key'].n;
idp.wellKnown(x);
browserid.lookup({ insecureSSL: true, domain: idp.domain() }, function(err) {
@ -31,6 +31,55 @@ describe('.well-known lookup, malformed', function() {
});
});
it('should handle missing public key', function(done) {
var x = idp.wellKnown();
delete x['public-key'];
idp.wellKnown(x);
browserid.lookup({ insecureSSL: true, domain: idp.domain() }, function(err) {
(err).should.contain("missing required property 'keys' and/or 'public-key'");
// repair well-known
idp.wellKnown(null);
done();
});
});
it('should handle empty list of public keys', function(done) {
var x = idp.wellKnown();
x.keys = [];
delete x['public-key'];
idp.wellKnown(x);
browserid.lookup({ insecureSSL: true, domain: idp.domain() }, function(err) {
(err).should.contain("missing required property 'keys' and/or 'public-key'");
// repair well-known
idp.wellKnown(null);
done();
});
});
it('should handle list of public keys', function(done) {
var x = idp.wellKnown();
x.keys = [x['public-key']];
delete x['public-key'];
idp.wellKnown(x);
browserid.lookup({ insecureSSL: true, domain: idp.domain() }, function(err, details) {
should.not.exist(err);
(details.publicKeys.length).should.equal(1);
details.publicKey.should.equal(details.publicKeys[0]);
// repair well-known
idp.wellKnown(null);
done();
});
});
it('should handle missing required fields', function(done) {
var x = idp.wellKnown();
delete x.provisioning;