Initial commit

This commit is contained in:
Sandrino Di Mattia
2016-07-18 14:21:15 +02:00
commit 505eb36cd5
11 changed files with 620 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
{
"presets": ["es2015", "stage-0"],
"ignore": [
"./node_modules/"
]
}
+34
View File
@@ -0,0 +1,34 @@
{
"parser": "babel-eslint",
"ecmaFeatures": {
"arrowFunctions": true,
"blockBindings": true,
"generators": true,
"modules": true
},
"rules": {
"array-bracket-spacing": [2, "always"],
"comma-dangle": [2, "never"],
"eol-last": 2,
"indent": [2, 2, {
"SwitchCase": 1
}],
"jsx-quotes": [2, "prefer-double"],
"no-multiple-empty-lines": 2,
"no-unused-vars": 2,
"no-var": 2,
"object-curly-spacing": [2, "always"],
"quotes": [2, "single", "avoid-escape"],
"semi": [2, "always"],
"strict": 0,
"space-before-blocks": [2, "always"]
},
"env": {
"amd": true,
"es6": true,
"node": true
},
"plugins": [
"babel"
]
}
+37
View File
@@ -0,0 +1,37 @@
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
node_modules
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# Build output
lib
!src/lib
+3
View File
@@ -0,0 +1,3 @@
.babelrc
.eslintrc
src/
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Sandrino Di Mattia
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+52
View File
@@ -0,0 +1,52 @@
# jwks-rsa
A library to retrieve RSA public keys from a JWKS (JSON Web Key Set) endpoint.
> npm install --save jwks-rsa
## Usage
```js
const jwksClient = require('jwksClient');
const client = jwksClient({
cache: true, // Default value
cacheMaxEntries: 5, // Default value
cacheMaxAge = ms('10h'), // Default value
strictSsl = true, // Default value
jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json'
});
const kid = 'RkI5MjI5OUY5ODc1N0Q4QzM0OUYzNkVGMTJDOUEzQkFCOTU3NjE2Rg';
client.getSigningKey(kid, (err, key) => {
const signingKey = key.publicKey || key.rsaPublicKey;
// Now I can use this to configure my Express or Hapi middleware
});
```
## Running Tests
```
npm run test
```
## Showing Trace Logs
To show trace logs you can set the following environment variable:
```
DEBUG=jwks
```
Output:
```
jwks Retrieving keys from http://my-authz-server/.well-known/jwks.json +5ms
jwks Keys: +8ms [ { alg: 'RS256',
kty: 'RSA',
use: 'sig',
x5c: [ 'pk1' ],
kid: 'ABC' },
{ alg: 'RS256', kty: 'RSA', use: 'sig', x5c: [], kid: '123' } ]
```
+48
View File
@@ -0,0 +1,48 @@
{
"name": "jwks-rsa",
"version": "0.1.0",
"description": "Library to retrieve RSA public keys from a JWKS endpoint",
"main": "lib/index.js",
"dependencies": {
"debug": "^2.2.0",
"lru-memoizer": "^1.6.0",
"ms": "^0.7.1",
"request": "^2.73.0"
},
"devDependencies": {
"babel-cli": "^6.9.0",
"babel-core": "^6.9.0",
"babel-eslint": "^6.0.4",
"babel-preset-es2015": "^6.9.0",
"babel-preset-stage-0": "^6.5.0",
"chai": "^3.5.0",
"eslint": "^2.10.2",
"eslint-plugin-babel": "^3.2.0",
"mocha": "^2.5.3",
"nock": "^8.0.0",
"rimraf": "^2.5.2"
},
"scripts": {
"clean": "rimraf lib/",
"compile": "babel -d lib/ src/",
"prepublish": "npm run clean && npm run compile",
"test": "NODE_ENV=test mocha --compilers js:babel-core/register --timeout 5000 $(find ./tests -name *.tests.js)",
"test-watch": "NODE_ENV=test mocha --compilers js:babel-core/register --timeout 5000 $(find ./tests -name *.tests.js) --watch",
"release": "git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish"
},
"repository": {
"type": "git",
"url": "git+https://github.com/sandrinodimattia/node-jwks-rsa.git"
},
"keywords": [
"jwks",
"rsa",
"jwt"
],
"author": "Sandrino Di Mattia",
"license": "MIT",
"bugs": {
"url": "https://github.com/sandrinodimattia/node-jwks-rsa/issues"
},
"homepage": "https://github.com/sandrinodimattia/node-jwks-rsa#readme"
}
+98
View File
@@ -0,0 +1,98 @@
import ms from 'ms';
import debug from 'debug';
import request from 'request';
import memoizer from 'lru-memoizer';
import { certToPEM, rsaPublicKeyToPEM } from './lib/utils';
export default class JwksClient {
constructor({ cache = true, cacheMaxEntries = 5, cacheMaxAge = ms('10h'), jwksUri, strictSsl = true } = options) {
this.logger = debug('jwks');
this.jwksUri = jwksUri;
this.strictSsl = strictSsl;
this.cacheMaxEntries = cacheMaxEntries;
this.cacheMaxAge = cacheMaxAge;
// Cached version to avoid too many roundtrips to the JWKS endpoint.
if (cache) {
this.getSigningKey = memoizer({
load: (kid, callback) => {
this._getSigningKeyInternal(kid, (err, key) => {
if (err) {
return callback(err);
}
this.logger(`Caching signing key for '${kid}':`, key);
return callback(null, key);
});
},
hash: (kid) => kid,
maxAge: this.cacheMaxAge,
max: this.cacheMaxEntries
});
} else {
this.getSigningKey = this._getSigningKeyInternal;
}
}
getKeys(cb) {
this.logger(`Retrieving keys from ${this.jwksUri}`);
request({ json: true, uri: this.jwksUri, strictSSL: this.strictSsl }, (err, res) => {
if (err || res.statusCode < 200 || res.statusCode >= 300) {
this.logger('Failure:', res && res.body || err);
if (res) {
return cb(new Error(res.body && (res.body.message || res.body) || res.statusMessage || `Http Error ${res.statusCode}`));
}
return cb(err);
}
this.logger('Keys:', res.body.keys);
return cb(null, res.body.keys);
});
}
getSigningKeys(cb) {
this.getKeys((err, keys) => {
if (err) {
return cb(err);
}
if (!keys || !keys.length) {
return cb(new Error('The JSON Web Key Set endpoint did not contain any keys'));
}
const signingKeys = keys
.filter(key => key.use === 'sig' && key.kty === 'RSA' && key.kid && ((key.x5c && key.x5c.length) || (key.n && key.e)))
.map(key => {
if (key.x5c && key.x5c.length) {
return { kid: key.kid, nbf: key.nbf, publicKey: certToPEM(key.x5c[0]) };
} else {
return { kid: key.kid, nbf: key.nbf, rsaPublicKey: rsaPublicKeyToPEM(key.n, key.e) };
}
});
if (!signingKeys.length) {
return cb(new Error('The JSON Web Key Set endpoint did not contain any signing keys'));
}
this.logger('Signing Keys:', signingKeys);
return cb(null, signingKeys);
});
}
_getSigningKeyInternal(kid, cb) {
this.getSigningKeys((err, keys) => {
if (err) {
return cb(err);
}
const key = keys.find(k => k.kid === kid);
if (key && key.length) {
cb(null, key[0]);
} else {
cb(null, null);
}
});
}
}
+47
View File
@@ -0,0 +1,47 @@
import JwksClient from './JwksClient';
module.exports = (options) => {
return new JwksClient(options);
};
module.exports.expressJwtSecretProvider = (options) => {
const client = new JwksClient(options);
return function secretProvider(req, header, payload, cb) {
if (header.alg !== 'RS256') {
return cb(new Error(`Unsupported algorithm '${header.alg}'`));
}
client.getSigningKey(header.kid, (err, key) => {
if (err) {
return cb(err);
}
if (!key) {
return cb(new Error(`Unable to find a signing key for '${header.id}'`));
}
return cb(null, key.publicKey || key.rsaPublicKey);
});
};
};
module.exports.hapiJwt2KeyProvider = (options) => {
const client = new JwksClient(options);
return function keyProvider(decoded, cb) {
if (decoded.alg !== 'RS256') {
return cb(new Error(`Unsupported algorithm '${header.alg}'`));
}
client.getSigningKey(decoded.kid, (err, key) => {
if (err) {
return cb(err);
}
if (!key) {
return cb(new Error(`Unable to find a signing key for '${header.id}'`));
}
return decoded(null, key.publicKey || key.rsaPublicKey, key);
});
};
};
+57
View File
@@ -0,0 +1,57 @@
export function certToPEM(cert) {
cert = cert.match(/.{1,64}/g).join('\n');
cert = `-----BEGIN CERTIFICATE-----\n${cert}\n-----END CERTIFICATE-----\n`;
return cert;
};
function prepadSigned(hexStr) {
const msb = hexStr[0];
if (msb < '0' || msb > '7') {
return `00${hexStr}`;
}
return hexStr;
}
function toHex(number) {
const nstr = number.toString(16);
if (nstr.length % 2) {
return `0${nstr}`;
}
return nstr;
}
function encodeLengthHex(n) {
if (n <= 127) {
return toHex(n);
}
const nHex = toHex(n);
const lengthOfLengthByte = 128 + nHex.length / 2;
return toHex(lengthOfLengthByte) + nHex;
}
/*
* Source: http://stackoverflow.com/questions/18835132/xml-to-pem-in-node-js
*/
export function rsaPublicKeyToPEM(modulusB64, exponentB64) {
const modulus = new Buffer(modulusB64, 'base64');
const exponent = new Buffer(exponentB64, 'base64');
const modulusHex = prepadSigned(modulus.toString('hex'));
const exponentHex = prepadSigned(exponent.toString('hex'));
const modlen = modulusHex.length / 2;
const explen = exponentHex.length / 2;
const encodedModlen = encodeLengthHex(modlen);
const encodedExplen = encodeLengthHex(explen);
const encodedPubkey = '30' +
encodeLengthHex(modlen + explen + encodedModlen.length / 2 + encodedExplen.length / 2 + 2) +
'02' + encodedModlen + modulusHex +
'02' + encodedExplen + exponentHex;
const der = new Buffer(encodedPubkey, 'hex')
.toString('base64');
let pem = `-----BEGIN RSA PUBLIC KEY-----\n`;
pem += `${der.match(/.{1,64}/g).join('\n')}`;
pem += `\n-----END RSA PUBLIC KEY-----\n`;
return pem;
};
+217
View File
@@ -0,0 +1,217 @@
import nock from 'nock';
import { expect } from 'chai';
import JwksClient from '../src/JwksClient';
describe('JwksClient', () => {
const jwksHost = 'http://my-authz-server';
beforeEach(() => {
nock.cleanAll();
});
describe('#getKeys', () => {
it('should handle errors', (done) => {
nock(jwksHost)
.get('/.well-known/jwks.json')
.reply(500, 'Unknown Server Error');
const client = new JwksClient({
jwksUri: `${jwksHost}/.well-known/jwks.json`
});
client.getKeys((err) => {
expect(err).not.to.be.null;
expect(err.message).to.equal('Unknown Server Error');
done();
});
});
it('should return keys', (done) => {
nock(jwksHost)
.get('/.well-known/jwks.json')
.reply(200, {
keys: [
{
alg: 'RS256',
kty: 'RSA',
use: 'sig',
x5c: [
'pk1'
],
kid: 'ABC'
},
{
alg: 'RS256',
kty: 'RSA',
use: 'sig',
x5c: [],
kid: '123'
}
]
});
const client = new JwksClient({
jwksUri: `${jwksHost}/.well-known/jwks.json`
});
client.getKeys((err, keys) => {
expect(err).to.be.null;
expect(keys).not.to.be.null;
expect(keys.length).to.equal(2);
expect(keys[1].kid).to.equal('123');
done();
});
});
});
describe('#getSigningKeys', () => {
it('should handle errors', (done) => {
nock(jwksHost)
.get('/.well-known/jwks.json')
.reply(500, 'Unknown Server Error');
const client = new JwksClient({
jwksUri: `${jwksHost}/.well-known/jwks.json`
});
client.getSigningKeys((err) => {
expect(err).not.to.be.null;
expect(err.message).to.equal('Unknown Server Error');
done();
});
});
it('should return signing keys (with x5c)', (done) => {
nock(jwksHost)
.get('/.well-known/jwks.json')
.reply(200, {
keys: [
{
alg: 'RS256',
kty: 'RSA',
use: 'sig',
x5c: [
'MIIDDTCCAfWgAwIBAgIJAJVkuSv2H8mDMA0GCSqGSIb3DQEBBQUAMB0xGzAZBgNVBAMMEnNhbmRyaW5vLmF1dGgwLmNvbTAeFw0xNDA1MTQyMTIyMjZaFw0yODAxMjEyMTIyMjZaMB0xGzAZBgNVBAMMEnNhbmRyaW5vLmF1dGgwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL6jWASkHhXz5Ug6t5BsYBrXDIgrWu05f3oq2fE+5J5REKJiY0Ddc+Kda34ZwOptnUoef3JwKPDAckTJQDugweNNZPwOmFMRKj4xqEpxEkIX8C+zHs41Q6x54ZZy0xU+WvTGcdjzyZTZ/h0iOYisswFQT/s6750tZG0BOBtZ5qS/80tmWH7xFitgewdWteJaASE/eO1qMtdNsp9fxOtN5U/pZDUyFm3YRfOcODzVqp3wOz+dcKb7cdZN11EYGZOkjEekpcedzHCo9H4aOmdKCpytqL/9FXoihcBMg39s1OW3cfwfgf5/kvOJdcqR4PoATQTfsDVoeMWVB4XLGR6SC5kCAwEAAaNQME4wHQYDVR0OBBYEFHDYn9BQdup1CoeoFi0Rmf5xn/W9MB8GA1UdIwQYMBaAFHDYn9BQdup1CoeoFi0Rmf5xn/W9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGLpQZdd2ICVnGjc6CYfT3VNoujKYWk7E0shGaCXFXptrZ8yaryfo6WAizTfgOpQNJH+Jz+QsCjvkRt6PBSYX/hb5OUDU2zNJN48/VOw57nzWdjI70H2Ar4oJLck36xkIRs/+QX+mSNCjZboRwh0LxanXeALHSbCgJkbzWbjVnfJEQUP9P/7NGf0MkO5I95C/Pz9g91y8gU+R3imGppLy9Zx+OwADFwKAEJak4JrNgcjHBQenakAXnXP6HG4hHH4MzO8LnLiKv8ZkKVL67da/80PcpO0miMNPaqBBMd2Cy6GzQYE0ag6k0nk+DMIFn7K+o21gjUuOEJqIbAvhbf2KcM='
],
n: 'vqNYBKQeFfPlSDq3kGxgGtcMiCta7Tl_eirZ8T7knlEQomJjQN1z4p1rfhnA6m2dSh5_cnAo8MByRMlAO6DB401k_A6YUxEqPjGoSnESQhfwL7MezjVDrHnhlnLTFT5a9MZx2PPJlNn-HSI5iKyzAVBP-zrvnS1kbQE4G1nmpL_zS2ZYfvEWK2B7B1a14loBIT947Woy102yn1_E603lT-lkNTIWbdhF85w4PNWqnfA7P51wpvtx1k3XURgZk6SMR6Slx53McKj0fho6Z0oKnK2ov_0VeiKFwEyDf2zU5bdx_B-B_n-S84l1ypHg-gBNBN-wNWh4xZUHhcsZHpILmQ',
e: 'AQAB',
kid: 'RkI5MjI5OUY5ODc1N0Q4QzM0OUYzNkVGMTJDOUEzQkFCOTU3NjE2Rg',
x5t: 'RkI5MjI5OUY5ODc1N0Q4QzM0OUYzNkVGMTJDOUEzQkFCOTU3NjE2Rg'
},
{
alg: 'RS256',
kty: 'RSA',
use: 'sig',
nbf: 123,
x5c: [
'MIIDGzCCAgOgAwIBAgIJAPQM5+PwmOcPMA0GCSqGSIb3DQEBCwUAMCQxIjAgBgNVBAMMGXNhbmRyaW5vLWRldi5ldS5hdXRoMC5jb20wHhcNMTUwMzMxMDkwNTQ3WhcNMjgxMjA3MDkwNTQ3WjAkMSIwIAYDVQQDDBlzYW5kcmluby1kZXYuZXUuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv/SECtT7H4rxKtX2HpGhSyeYTe3Vet8YQpjBAr+1TnQ1fcYfvfmnVRHvhmTwABktD1erF1lxFsrRw92yBDOHlL7lj1n2fcfLftSoStgvRHVg52kR+CkBVQ6/mF1lYkefIjik6YRMf55Eu4FqDyVG2dgd5EA8kNO4J8OPc7vAtZyXrRYOZjVXbEgyjje/V+OpMQxAHP2Er11TLuzJjioP0ICVqhAZdq2sLk7agoxn64md6fqOk4N+7lJkU4+412VD0qYwKxD7nGsEclYawKoZD9/xhCk2qfQ/HptIumrdQ5ox3Sq5t2a7VKa41dBUQ1MQtXG2iY7S9RlfcMIyQwGhOQIDAQABo1AwTjAdBgNVHQ4EFgQUHpS1fvO/54G2c1VpEDNUZRSl44gwHwYDVR0jBBgwFoAUHpS1fvO/54G2c1VpEDNUZRSl44gwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAtm9I0nr6eXF5aq4yllfiqZcQ6mKrJLH9Rm4Jv+olniNynTcnpwprRVLToIawc8MmzIGZTtCn7u+dSxWf1UNE+SH7XgEnGtO74239vleEx1+Tf5viIdsnCxgvFiPdOqRlc9KcFSWd6a7RzcglnyU7GEx0K5GLv1wPA6qEM+3uwNwjAyVSu5dFw8kCfaSvlk5rXKRUzSoW9NVomw6+tADR8vMZS+4KThZ+4GH0rMN4KjIaRFxW8OMVYOn12uq33fLCd6MuPHW/rklxLbQBoHIU/ClNhbD0t6f00w9lHhPy4IP73rv7Oow0Ny6i70Iq0ijqj+kAtnrphlOvLFxqn6nCvQ=='
],
n: 'v_SECtT7H4rxKtX2HpGhSyeYTe3Vet8YQpjBAr-1TnQ1fcYfvfmnVRHvhmTwABktD1erF1lxFsrRw92yBDOHlL7lj1n2fcfLftSoStgvRHVg52kR-CkBVQ6_mF1lYkefIjik6YRMf55Eu4FqDyVG2dgd5EA8kNO4J8OPc7vAtZyXrRYOZjVXbEgyjje_V-OpMQxAHP2Er11TLuzJjioP0ICVqhAZdq2sLk7agoxn64md6fqOk4N-7lJkU4-412VD0qYwKxD7nGsEclYawKoZD9_xhCk2qfQ_HptIumrdQ5ox3Sq5t2a7VKa41dBUQ1MQtXG2iY7S9RlfcMIyQwGhOQ',
e: 'AQAB',
kid: 'NkFCNEE1NDFDNTQ5RTQ5OTE1QzRBMjYyMzY0NEJCQTJBMjJBQkZCMA',
x5t: 'NkFCNEE1NDFDNTQ5RTQ5OTE1QzRBMjYyMzY0NEJCQTJBMjJBQkZCMA'
}
]
});
const client = new JwksClient({
jwksUri: `${jwksHost}/.well-known/jwks.json`
});
client.getSigningKeys((err, keys) => {
expect(err).to.be.null;
expect(keys).not.to.be.null;
expect(keys.length).to.equal(2);
expect(keys[0].publicKey).not.to.be.null;
expect(keys[0].kid).to.equal('RkI5MjI5OUY5ODc1N0Q4QzM0OUYzNkVGMTJDOUEzQkFCOTU3NjE2Rg');
expect(keys[1].kid).to.equal('NkFCNEE1NDFDNTQ5RTQ5OTE1QzRBMjYyMzY0NEJCQTJBMjJBQkZCMA');
expect(keys[1].publicKey).not.to.be.null;
expect(keys[1].nbf).to.equal(123);
done();
});
});
it('should return signing keys (mod/exp)', (done) => {
nock(jwksHost)
.get('/.well-known/jwks.json')
.reply(200, {
keys: [
{
kid: 'IdTokenSigningKeyContainer',
use: 'sig',
kty: 'RSA',
e: 'AQAB',
n: 'tLDZVZ2Eq_DFwNp24yeSq_Ha0MYbYOJs_WXIgVxQGabu5cZ9561OUtYWdB6xXXZLaZxFG02P5U2rC_CT1r0lPfC_KHYrviJ5Y_Ekif7iFV_1omLAiRksQziwA1i-hND32N5kxwEGNmZViVjWMBZ43wbIdWss4IMhrJy1WNQ07Fqp1Ee6o7QM1hTBve7bbkJkUAfjtC7mwIWqZdWoYIWBTZRXvhMgs_Aeb_pnDekosqDoWQ5aMklk3NvaaBBESqlRAJZUUf5WDFoJh7yRELOFF4lWJxtArTEiQPWVTX6PCs0klVPU6SRQqrtc4kKLCp1AC5EJqPYRGiEJpSz2nUhmAQ'
},
{
kid: 'IdTokenSigningKeyContainer.v2',
nbf: 1459289287,
use: 'sig',
kty: 'RSA',
e: 'AQAB',
n: 's4W7xjkQZP3OwG7PfRgcYKn8eRYXHiz1iK503fS-K2FZo-Ublwwa2xFZWpsUU_jtoVCwIkaqZuo6xoKtlMYXXvfVHGuKBHEBVn8b8x_57BQWz1d0KdrNXxuMvtFe6RzMqiMqzqZrzae4UqVCkYqcR9gQx66Ehq7hPmCxJCkg7ajo7fu6E7dPd34KH2HSYRsaaEA_BcKTeb9H1XE_qEKjog68wUU9Ekfl3FBIRN-1Ah_BoktGFoXyi_jt0-L0-gKcL1BLmUlGzMusvRbjI_0-qj-mc0utGdRjY-xIN2yBj8vl4DODO-wMwfp-cqZbCd9TENyHaTb8iA27s-73L3ExOQ'
}
]
});
const client = new JwksClient({
jwksUri: `${jwksHost}/.well-known/jwks.json`
});
client.getSigningKeys((err, keys) => {
expect(err).to.be.null;
expect(keys).not.to.be.null;
expect(keys.length).to.equal(2);
expect(keys[0].rsaPublicKey).not.to.be.null;
expect(keys[0].kid).to.equal('IdTokenSigningKeyContainer');
expect(keys[1].kid).to.equal('IdTokenSigningKeyContainer.v2');
expect(keys[1].rsaPublicKey).not.to.be.null;
expect(keys[1].nbf).to.equal(1459289287);
done();
});
});
it('should only take the signing keys from the keys', (done) => {
nock(jwksHost)
.get('/.well-known/jwks.json')
.reply(200, {
keys: [
{
kty: 'something',
use: 'else',
x5c: [
'MIIDDTCCAfWgAwIBAgIJAJVkuSv2H8mDMA0GCSqGSIb3DQEBBQUAMB0xGzAZBgNVBAMMEnNhbmRyaW5vLmF1dGgwLmNvbTAeFw0xNDA1MTQyMTIyMjZaFw0yODAxMjEyMTIyMjZaMB0xGzAZBgNVBAMMEnNhbmRyaW5vLmF1dGgwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL6jWASkHhXz5Ug6t5BsYBrXDIgrWu05f3oq2fE+5J5REKJiY0Ddc+Kda34ZwOptnUoef3JwKPDAckTJQDugweNNZPwOmFMRKj4xqEpxEkIX8C+zHs41Q6x54ZZy0xU+WvTGcdjzyZTZ/h0iOYisswFQT/s6750tZG0BOBtZ5qS/80tmWH7xFitgewdWteJaASE/eO1qMtdNsp9fxOtN5U/pZDUyFm3YRfOcODzVqp3wOz+dcKb7cdZN11EYGZOkjEekpcedzHCo9H4aOmdKCpytqL/9FXoihcBMg39s1OW3cfwfgf5/kvOJdcqR4PoATQTfsDVoeMWVB4XLGR6SC5kCAwEAAaNQME4wHQYDVR0OBBYEFHDYn9BQdup1CoeoFi0Rmf5xn/W9MB8GA1UdIwQYMBaAFHDYn9BQdup1CoeoFi0Rmf5xn/W9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGLpQZdd2ICVnGjc6CYfT3VNoujKYWk7E0shGaCXFXptrZ8yaryfo6WAizTfgOpQNJH+Jz+QsCjvkRt6PBSYX/hb5OUDU2zNJN48/VOw57nzWdjI70H2Ar4oJLck36xkIRs/+QX+mSNCjZboRwh0LxanXeALHSbCgJkbzWbjVnfJEQUP9P/7NGf0MkO5I95C/Pz9g91y8gU+R3imGppLy9Zx+OwADFwKAEJak4JrNgcjHBQenakAXnXP6HG4hHH4MzO8LnLiKv8ZkKVL67da/80PcpO0miMNPaqBBMd2Cy6GzQYE0ag6k0nk+DMIFn7K+o21gjUuOEJqIbAvhbf2KcM='
],
n: 'vqNYBKQeFfPlSDq3kGxgGtcMiCta7Tl_eirZ8T7knlEQomJjQN1z4p1rfhnA6m2dSh5_cnAo8MByRMlAO6DB401k_A6YUxEqPjGoSnESQhfwL7MezjVDrHnhlnLTFT5a9MZx2PPJlNn-HSI5iKyzAVBP-zrvnS1kbQE4G1nmpL_zS2ZYfvEWK2B7B1a14loBIT947Woy102yn1_E603lT-lkNTIWbdhF85w4PNWqnfA7P51wpvtx1k3XURgZk6SMR6Slx53McKj0fho6Z0oKnK2ov_0VeiKFwEyDf2zU5bdx_B-B_n-S84l1ypHg-gBNBN-wNWh4xZUHhcsZHpILmQ',
e: 'AQAB',
kid: 'RkI5MjI5OUY5ODc1N0Q4QzM0OUYzNkVGMTJDOUEzQkFCOTU3NjE2Rg',
x5t: 'RkI5MjI5OUY5ODc1N0Q4QzM0OUYzNkVGMTJDOUEzQkFCOTU3NjE2Rg'
},
{
kty: 'something',
use: 'else',
nbf: 123,
x5c: [
'MIIDGzCCAgOgAwIBAgIJAPQM5+PwmOcPMA0GCSqGSIb3DQEBCwUAMCQxIjAgBgNVBAMMGXNhbmRyaW5vLWRldi5ldS5hdXRoMC5jb20wHhcNMTUwMzMxMDkwNTQ3WhcNMjgxMjA3MDkwNTQ3WjAkMSIwIAYDVQQDDBlzYW5kcmluby1kZXYuZXUuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv/SECtT7H4rxKtX2HpGhSyeYTe3Vet8YQpjBAr+1TnQ1fcYfvfmnVRHvhmTwABktD1erF1lxFsrRw92yBDOHlL7lj1n2fcfLftSoStgvRHVg52kR+CkBVQ6/mF1lYkefIjik6YRMf55Eu4FqDyVG2dgd5EA8kNO4J8OPc7vAtZyXrRYOZjVXbEgyjje/V+OpMQxAHP2Er11TLuzJjioP0ICVqhAZdq2sLk7agoxn64md6fqOk4N+7lJkU4+412VD0qYwKxD7nGsEclYawKoZD9/xhCk2qfQ/HptIumrdQ5ox3Sq5t2a7VKa41dBUQ1MQtXG2iY7S9RlfcMIyQwGhOQIDAQABo1AwTjAdBgNVHQ4EFgQUHpS1fvO/54G2c1VpEDNUZRSl44gwHwYDVR0jBBgwFoAUHpS1fvO/54G2c1VpEDNUZRSl44gwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAtm9I0nr6eXF5aq4yllfiqZcQ6mKrJLH9Rm4Jv+olniNynTcnpwprRVLToIawc8MmzIGZTtCn7u+dSxWf1UNE+SH7XgEnGtO74239vleEx1+Tf5viIdsnCxgvFiPdOqRlc9KcFSWd6a7RzcglnyU7GEx0K5GLv1wPA6qEM+3uwNwjAyVSu5dFw8kCfaSvlk5rXKRUzSoW9NVomw6+tADR8vMZS+4KThZ+4GH0rMN4KjIaRFxW8OMVYOn12uq33fLCd6MuPHW/rklxLbQBoHIU/ClNhbD0t6f00w9lHhPy4IP73rv7Oow0Ny6i70Iq0ijqj+kAtnrphlOvLFxqn6nCvQ=='
],
n: 'v_SECtT7H4rxKtX2HpGhSyeYTe3Vet8YQpjBAr-1TnQ1fcYfvfmnVRHvhmTwABktD1erF1lxFsrRw92yBDOHlL7lj1n2fcfLftSoStgvRHVg52kR-CkBVQ6_mF1lYkefIjik6YRMf55Eu4FqDyVG2dgd5EA8kNO4J8OPc7vAtZyXrRYOZjVXbEgyjje_V-OpMQxAHP2Er11TLuzJjioP0ICVqhAZdq2sLk7agoxn64md6fqOk4N-7lJkU4-412VD0qYwKxD7nGsEclYawKoZD9_xhCk2qfQ_HptIumrdQ5ox3Sq5t2a7VKa41dBUQ1MQtXG2iY7S9RlfcMIyQwGhOQ',
e: 'AQAB',
kid: 'NkFCNEE1NDFDNTQ5RTQ5OTE1QzRBMjYyMzY0NEJCQTJBMjJBQkZCMA',
x5t: 'NkFCNEE1NDFDNTQ5RTQ5OTE1QzRBMjYyMzY0NEJCQTJBMjJBQkZCMA'
}
]
});
const client = new JwksClient({
jwksUri: `${jwksHost}/.well-known/jwks.json`
});
client.getSigningKeys((err) => {
expect(err).not.to.be.null;
expect(err.message).to.equal('The JSON Web Key Set endpoint did not contain any signing keys');
done();
});
});
});
});