implement idp and RP extended claim extraction, use this to test support for extensions like unverified-email
This commit is contained in:
Родитель
dc31f5d590
Коммит
6327597091
|
@ -14,6 +14,49 @@ util = require('util');
|
|||
require("jwcrypto/lib/algs/ds");
|
||||
require("jwcrypto/lib/algs/rs");
|
||||
|
||||
// a list of reserved claims from jwt's spec and including
|
||||
// browserid reserved (but "private") claims.
|
||||
const jwtRegisteredClaimNames = [
|
||||
'iss',
|
||||
'sub',
|
||||
'aud',
|
||||
'exp',
|
||||
'nbf',
|
||||
'iat',
|
||||
'jti',
|
||||
'public-key',
|
||||
'principal'
|
||||
];
|
||||
|
||||
// given a payload (from assertion or certificate), extract
|
||||
// "extra" claims embedded therein. To conform with JWT, these are
|
||||
// assumed to be un-recognized top level properties. A historical exception
|
||||
// is 'principal'. If principal is an object, all claims other than 'email'
|
||||
// will be extracted and returned as if they were proper top level jwt
|
||||
// extensions.
|
||||
function extractExtraClaims(claims) {
|
||||
var extraClaims = {};
|
||||
Object.keys(claims).forEach(function(key) {
|
||||
if (jwtRegisteredClaimNames.indexOf(key) === -1) {
|
||||
extraClaims[key] = claims[key];
|
||||
}
|
||||
});
|
||||
// now extract unknown fields from 'principal' object as if they
|
||||
// were proper top level extensions
|
||||
if (typeof claims.principal === 'object') {
|
||||
Object.keys(claims.principal).forEach(function(key) {
|
||||
// only overlay non-reserved, non 'email', embedded claims
|
||||
// that do not exist at the top level.
|
||||
if (jwtRegisteredClaimNames.indexOf(key) === -1 &&
|
||||
key !== 'email' &&
|
||||
!extraClaims[key]) {
|
||||
extraClaims[key] = claims.principal[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
return Object.keys(extraClaims).length ? extraClaims : null;
|
||||
}
|
||||
|
||||
function extractDomainFromEmail(email) {
|
||||
return (/\@(.*)$/).exec(email)[1].toLowerCase();
|
||||
}
|
||||
|
@ -23,6 +66,11 @@ function extractDomainFromEmail(email) {
|
|||
function verify(args, browserid, assertion, audience, cb) {
|
||||
var ultimateIssuer;
|
||||
|
||||
// we will manually unpack the certificate (IdP issued) and assertion
|
||||
// (UA generated). jwcrypto's API's are insufficient to allow us to relay
|
||||
// claims made by each back to the user, which limits extensibility.
|
||||
var idpClaims, uaClaims;
|
||||
|
||||
// first we must determine the principal email that this assertion vouches for.
|
||||
// BrowserID support document lookup requires that support documents are fetched
|
||||
// with the domain of the principal email to allow IdP's to serve dynamic
|
||||
|
@ -31,10 +79,11 @@ function verify(args, browserid, assertion, audience, cb) {
|
|||
try {
|
||||
var email = null;
|
||||
var bundle = jwcrypto.cert.unbundle(assertion);
|
||||
// principal will be in the last certificate in the chain.
|
||||
var payload = jwcrypto.extractComponents(bundle.certs[bundle.certs.length - 1]).payload;
|
||||
if (payload.principal) email = payload.principal.email;
|
||||
if (!email) email = payload.sub;
|
||||
// idp's claims come from the last certificate in the chain.
|
||||
idpClaims = jwcrypto.extractComponents(bundle.certs[bundle.certs.length - 1]).payload;
|
||||
uaClaims = jwcrypto.extractComponents(bundle.signedAssertion).payload;
|
||||
if (idpClaims.principal) email = idpClaims.principal.email;
|
||||
if (!email) email = idpClaims.sub;
|
||||
principalDomain = extractDomainFromEmail(email);
|
||||
} catch(e) {
|
||||
// if we fail to extract principle domain, we will rely on subsequent verification
|
||||
|
@ -55,7 +104,7 @@ function verify(args, browserid, assertion, audience, cb) {
|
|||
// returned issuer will be the last cert in the chain
|
||||
ultimateIssuer = issuer;
|
||||
|
||||
// let's go fetch the public key for this host
|
||||
// let's go fetch the public key for this issuer
|
||||
browserid.lookup(issuer, principalDomain, function(err, details) {
|
||||
if (err) return cb(err);
|
||||
next(null, details.publicKey);
|
||||
|
@ -74,9 +123,6 @@ function verify(args, browserid, assertion, audience, cb) {
|
|||
return cb("audience mismatch: " + err);
|
||||
}
|
||||
|
||||
// principal is in the last certificate
|
||||
var principal = certParamsArray[certParamsArray.length - 1].certParams.principal;
|
||||
|
||||
// build up a response object
|
||||
var obj = {
|
||||
audience: assertionParams.audience,
|
||||
|
@ -84,9 +130,17 @@ function verify(args, browserid, assertion, audience, cb) {
|
|||
issuer: ultimateIssuer
|
||||
};
|
||||
|
||||
if (principal.email) obj.email = principal.email;
|
||||
if (idpClaims && idpClaims.principal && idpClaims.principal.email) {
|
||||
obj.email = idpClaims.principal.email;
|
||||
}
|
||||
|
||||
// XXX: include other signed information from ceritificate and assertion
|
||||
// extract extra idp claims
|
||||
var extClaims = extractExtraClaims(idpClaims);
|
||||
if (extClaims) obj.idpClaims = extClaims;
|
||||
|
||||
// extract extra ua claims
|
||||
extClaims = extractExtraClaims(uaClaims);
|
||||
if (extClaims) obj.uaClaims = extClaims;
|
||||
|
||||
// If the caller has expressed trust in a set of issuers, then we need not verify
|
||||
// that those issuers can speak for the principal.
|
||||
|
|
|
@ -2,6 +2,6 @@ exports.name = "log into local persona with unverified email";
|
|||
exports.audience = "http://127.0.0.1:10001";
|
||||
exports.issuer = "127.0.0.1";
|
||||
exports.trustedIssuers = [ '127.0.0.1' ];
|
||||
exports.idpClaims = { 'unverified-email': 'test@example.com' };
|
||||
exports.assertion = "eyJhbGciOiJSUzI1NiJ9.eyJwdWJsaWMta2V5Ijp7ImFsZ29yaXRobSI6IkRTIiwieSI6ImVhMDk1YmZhMzBjMGJhY2QzYThkZjE3ZGUzMzU5YmQ3ZTdlNDJhY2FhNGU2MTdlZGYxMmQ2Mzc5OTJlMjQwMGNiN2VkOGYzM2EzNmE5OTQzOWE3ZGJmZjc4NDM4OWYxODVkODNkMTI3YWY5NmUyZmI2MmY3ZTAwY2FkNTUwYmZjZTlhNDJmMjlhYThlMjkyZDA1OTAzODM5MDU3Y2YyOTIyMWUxZTMxZjJhNDE3ZWU2MzE4NjlhMGQ2OWJmYTRlMWExMTJiNGE4YmY2NjU2NjkyNDRhYjFiODMzNjU4YTRkYzczNjcxNDRmMDlkNTRmYzcxMGM0OThjOTgwODgzMWIiLCJwIjoiZmY2MDA0ODNkYjZhYmZjNWI0NWVhYjc4NTk0YjM1MzNkNTUwZDlmMWJmMmE5OTJhN2E4ZGFhNmRjMzRmODA0NWFkNGU2ZTBjNDI5ZDMzNGVlZWFhZWZkN2UyM2Q0ODEwYmUwMGU0Y2MxNDkyY2JhMzI1YmE4MWZmMmQ1YTViMzA1YThkMTdlYjNiZjRhMDZhMzQ5ZDM5MmUwMGQzMjk3NDRhNTE3OTM4MDM0NGU4MmExOGM0NzkzMzQzOGY4OTFlMjJhZWVmODEyZDY5YzhmNzVlMzI2Y2I3MGVhMDAwYzNmNzc2ZGZkYmQ2MDQ2MzhjMmVmNzE3ZmMyNmQwMmUxNyIsInEiOiJlMjFlMDRmOTExZDFlZDc5OTEwMDhlY2FhYjNiZjc3NTk4NDMwOWMzIiwiZyI6ImM1MmE0YTBmZjNiN2U2MWZkZjE4NjdjZTg0MTM4MzY5YTYxNTRmNGFmYTkyOTY2ZTNjODI3ZTI1Y2ZhNmNmNTA4YjkwZTVkZTQxOWUxMzM3ZTA3YTJlOWUyYTNjZDVkZWE3MDRkMTc1ZjhlYmY2YWYzOTdkNjllMTEwYjk2YWZiMTdjN2EwMzI1OTMyOWU0ODI5YjBkMDNiYmM3ODk2YjE1YjRhZGU1M2UxMzA4NThjYzM0ZDk2MjY5YWE4OTA0MWY0MDkxMzZjNzI0MmEzODg5NWM5ZDViY2NhZDRmMzg5YWYxZDdhNGJkMTM5OGJkMDcyZGZmYTg5NjIzMzM5N2EifSwicHJpbmNpcGFsIjp7InVudmVyaWZpZWQtZW1haWwiOiJ0ZXN0QGV4YW1wbGUuY29tIn0sImlhdCI6MTM4NTEwNzkxNDkyNiwiZXhwIjoxMzg1MTk0MzE0OTI2LCJpc3MiOiIxMjcuMC4wLjEifQ.F4WrockIsJcTtsN-7G9j4iKe47pMKeFTcaizILZmO5kRHubJSyfSeRTciYLSaNwOe-lWb7PuwSqXELt3WrJbbzKyuL0eTOEwDyt9SEeokJ1fbJx_qTgxgaWspdGM7K1AHytheZjmJxbfYwT62-EPdcjURauDQmuv4rCpJK9VoM-sk4bU8kqz5Kk3kuSsxVMutawsMqAmj2IFM3Z9BI4vu431S1RYCz45LkIsOnoUDHxGEit8TJFA4yHsZhKM1kA-gI-0oAGZwabMigqA5P77Sj6Zm8hdL5QVx4BWvZRo0JGLxEomdsdaOxJfreE1vaBLlI486KnCo_KKSNMgz1NXLg~eyJhbGciOiJEUzEyOCJ9.eyJleHAiOjEzODUxMDgwMzQ5NjUsImF1ZCI6Imh0dHA6Ly8xMjcuMC4wLjE6MTAwMDEifQ.FcbFm2WvcR47OWfx8UocfYmjiSSGnvw9tNqe3fXknIowXlhfnyaakQ";
|
||||
exports.pubKey = {"algorithm":"RS","n":"17595012132331458098548134117831642220733917454738717976837362203747279823707225986007560271270149923639755639304249562362407183825328959770633164330555301242010365883323439263007097451215211399222532134427235531685404214863016222484880748154869429815026061834503573980605658331770948685676884127063595107588866737394427126673107300720716575644182812349336870310674229883503572854039247475842196570552437882248316550099015712869747908213610091304070068372739904649774604377156004904519916041795202881382044134391128030488233741004446856148202051069703924785520815101647856064715670603325979078356417702814451716237349","e":"65537"};
|
||||
|
||||
|
|
|
@ -76,7 +76,15 @@ describe('assertion regression tests', function() {
|
|||
(details.audience).should.equal(c.audience);
|
||||
if (c.email) (details.email).should.equal(c.email);
|
||||
(details.issuer).should.equal(c.issuer);
|
||||
// XXX: test addition RP and IDP claims
|
||||
// if additional idpClaims exist, test them here.
|
||||
if (c.idpClaims) {
|
||||
(details.idpClaims).should.exist;
|
||||
(details.idpClaims).should.be.type('object');
|
||||
Object.keys(c.idpClaims).forEach(function(claim) {
|
||||
details.idpClaims.should.have.property(claim);
|
||||
details.idpClaims[claim].should.equal(c.idpClaims[claim]);
|
||||
});
|
||||
}
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче