mirror of
https://github.com/hanzoai/node-jwks-rsa.git
synced 2026-09-24 17:00:54 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"presets": ["es2015", "stage-0"],
|
||||
"ignore": [
|
||||
"./node_modules/"
|
||||
]
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
.babelrc
|
||||
.eslintrc
|
||||
src/
|
||||
@@ -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.
|
||||
@@ -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' } ]
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user