mirror of
https://github.com/hanzoai/node-jsonwebtoken.git
synced 2026-09-24 17:00:48 +00:00
Merge pull request from GHSA-8cf7-32gw-wr33
* Check if node version supports asymmetricKeyDetails * Validate algorithms for ec key type * Rename variable * Rename function * Add early return for symmetric keys * Validate algorithm for RSA key type * Validate algorithm for RSA-PSS key type * Check key types for EdDSA algorithm * Rename function * Move validateKey function to module * Convert arrow to function notation * Validate key in verify function * Simplify if * Convert if to switch..case * Guard against empty key in validation * Remove empty line * Add lib to check modulus length * Add modulus length checks * Validate mgf1HashAlgorithm and saltLength * Check node version before using key details API * Use built-in modulus length getter * Fix Node version validations * Remove duplicate validateKey * Add periods to error messages * Fix validation in verify function * Make asymmetric key validation the latest validation step * Change key curve validation * Remove support for ES256K * Fix old test that was using wrong key types to sign tokens * Enable RSA-PSS for old Node versions * Add specific RSA-PSS validations on Node 16 LTS+ * Improve error message * Simplify key validation code * Fix typo * Improve error message * Change var to const in test * Change const to let to avoid reassigning problem * Improve error message * Test incorrect private key type * Rename invalid to unsupported * Test verifying of jwt token with unsupported key * Test invalid private key type * Change order of object parameters * Move validation test to separate file * Move all validation tests to separate file * Add prime256v1 ec key * Remove modulus length check * WIP: Add EC key validation tests * Fix node version checks * Fix error message check on test * Add successful tests for EC curve check * Remove only from describe * Remove `only` * Remove duplicate block of code * Move variable to a different scope and make it const * Convert allowed curves to object for faster lookup * Rename variable * Change variable assignment order * Remove unused object properties * Test RSA-PSS happy path and wrong length * Add missing tests * Pass validation if no algorithm has been provided * Test validation of invalid salt length * Test error when signing token with invalid key * Change var to const/let in verify tests * Test verifying token with invalid key * Improve test error messages * Add parameter to skip private key validation * Replace DSA key with a 4096 bit long key * Test allowInvalidPrivateKeys in key signing * Improve test message * Rename variable * Add key validation flag tests * Fix variable name in Readme * Change private to public dsa key in verify * Rename flag * Run EC validation tests conditionally * Fix tests in old node versions * Ignore block of code from test coverage * Separate EC validations tests into two different ones * Add comment * Wrap switch in if instead of having an early return * Remove unsupported algorithms from asymmetric key validation * Rename option to allowInvalidAsymmetricKeyTypes and improve Readme * 9.0.0 * adding migration notes to readme * adding changelog for version 9.0.0 Co-authored-by: julienwoll <[email protected]>
This commit is contained in:
committed by
GitHub
co-authored by
julienwoll
parent
ad0184878d
commit
925d95de54
@@ -4,6 +4,24 @@
|
||||
All notable changes to this project will be documented in this file starting from version **v4.0.0**.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 9.0.0 - 2022-12-21
|
||||
|
||||
**Breaking changes: See [Migration from v8 to v9](https://github.com/auth0/node-jsonwebtoken/wiki/Migration-Notes:-v8-to-v9)**
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- Removed support for Node versions 11 and below.
|
||||
- The verify() function no longer accepts unsigned tokens by default. ([834503079514b72264fd13023a3b8d648afd6a16]https://github.com/auth0/node-jsonwebtoken/commit/834503079514b72264fd13023a3b8d648afd6a16)
|
||||
- RSA key size must be 2048 bits or greater. ([ecdf6cc6073ea13a7e71df5fad043550f08d0fa6]https://github.com/auth0/node-jsonwebtoken/commit/ecdf6cc6073ea13a7e71df5fad043550f08d0fa6)
|
||||
- Key types must be valid for the signing / verification algorithm
|
||||
|
||||
### Security fixes
|
||||
|
||||
- security: fixes `Arbitrary File Write via verify function` - CVE-2022-23529
|
||||
- security: fixes `Insecure default algorithm in jwt.verify() could lead to signature validation bypass` - CVE-2022-23540
|
||||
- security: fixes `Insecure implementation of key retrieval function could lead to Forgeable Public/Private Tokens from RSA to HMAC` - CVE-2022-23541
|
||||
- security: fixes `Unrestricted key type could lead to legacy keys usage` - CVE-2022-23539
|
||||
|
||||
## 8.5.1 - 2019-03-18
|
||||
|
||||
### Bug fix
|
||||
|
||||
@@ -17,6 +17,7 @@ $ npm install jsonwebtoken
|
||||
|
||||
# Migration notes
|
||||
|
||||
* [From v8 to v9](https://github.com/auth0/node-jsonwebtoken/wiki/Migration-Notes:-v8-to-v9)
|
||||
* [From v7 to v8](https://github.com/auth0/node-jsonwebtoken/wiki/Migration-Notes:-v7-to-v8)
|
||||
|
||||
# Usage
|
||||
@@ -52,6 +53,7 @@ When signing with RSA algorithms the minimum modulus length is 2048 except when
|
||||
* `keyid`
|
||||
* `mutatePayload`: if true, the sign function will modify the payload object directly. This is useful if you need a raw reference to the payload after claims have been applied to it but before it has been encoded into a token.
|
||||
* `allowInsecureKeySizes`: if true allows private keys with a modulus below 2048 to be used for RSA
|
||||
* `allowInvalidAsymmetricKeyTypes`: if true, allows asymmetric keys which do not match the specified algorithm. This option is intended only for backwards compatability and should be avoided.
|
||||
|
||||
|
||||
|
||||
@@ -158,7 +160,7 @@ As mentioned in [this comment](https://github.com/auth0/node-jsonwebtoken/issues
|
||||
> Eg: `1000`, `"2 days"`, `"10h"`, `"7d"`. A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default (`"120"` is equal to `"120ms"`).
|
||||
* `clockTimestamp`: the time in seconds that should be used as the current time for all necessary comparisons.
|
||||
* `nonce`: if you want to check `nonce` claim, provide a string value here. It is used on Open ID for the ID Tokens. ([Open ID implementation notes](https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes))
|
||||
|
||||
* `allowInvalidAsymmetricKeyTypes`: if true, allows asymmetric keys which do not match the specified algorithm. This option is intended only for backwards compatability and should be avoided.
|
||||
|
||||
```js
|
||||
// verify a token symmetric - synchronous
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
const semver = require('semver');
|
||||
|
||||
module.exports = semver.satisfies(process.version, '>=15.7.0');
|
||||
@@ -0,0 +1,3 @@
|
||||
const semver = require('semver');
|
||||
|
||||
module.exports = semver.satisfies(process.version, '>=16.9.0');
|
||||
@@ -0,0 +1,66 @@
|
||||
const ASYMMETRIC_KEY_DETAILS_SUPPORTED = require('./asymmetricKeyDetailsSupported');
|
||||
const RSA_PSS_KEY_DETAILS_SUPPORTED = require('./rsaPssKeyDetailsSupported');
|
||||
|
||||
const allowedAlgorithmsForKeys = {
|
||||
'ec': ['ES256', 'ES384', 'ES512'],
|
||||
'rsa': ['RS256', 'PS256', 'RS384', 'PS384', 'RS512', 'PS512'],
|
||||
'rsa-pss': ['PS256', 'PS384', 'PS512']
|
||||
};
|
||||
|
||||
const allowedCurves = {
|
||||
ES256: 'prime256v1',
|
||||
ES384: 'secp384r1',
|
||||
ES512: 'secp521r1',
|
||||
};
|
||||
|
||||
module.exports = function(algorithm, key) {
|
||||
if (!algorithm || !key) return;
|
||||
|
||||
const keyType = key.asymmetricKeyType;
|
||||
if (!keyType) return;
|
||||
|
||||
const allowedAlgorithms = allowedAlgorithmsForKeys[keyType];
|
||||
|
||||
if (!allowedAlgorithms) {
|
||||
throw new Error(`Unknown key type "${keyType}".`);
|
||||
}
|
||||
|
||||
if (!allowedAlgorithms.includes(algorithm)) {
|
||||
throw new Error(`"alg" parameter for "${keyType}" key type must be one of: ${allowedAlgorithms.join(', ')}.`)
|
||||
}
|
||||
|
||||
/*
|
||||
* Ignore the next block from test coverage because it gets executed
|
||||
* conditionally depending on the Node version. Not ignoring it would
|
||||
* prevent us from reaching the target % of coverage for versions of
|
||||
* Node under 15.7.0.
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
if (ASYMMETRIC_KEY_DETAILS_SUPPORTED) {
|
||||
switch (keyType) {
|
||||
case 'ec':
|
||||
const keyCurve = key.asymmetricKeyDetails.namedCurve;
|
||||
const allowedCurve = allowedCurves[algorithm];
|
||||
|
||||
if (keyCurve !== allowedCurve) {
|
||||
throw new Error(`"alg" parameter "${algorithm}" requires curve "${allowedCurve}".`);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'rsa-pss':
|
||||
if (RSA_PSS_KEY_DETAILS_SUPPORTED) {
|
||||
const length = parseInt(algorithm.slice(-3), 10);
|
||||
const { hashAlgorithm, mgf1HashAlgorithm, saltLength } = key.asymmetricKeyDetails;
|
||||
|
||||
if (hashAlgorithm !== `sha${length}` || mgf1HashAlgorithm !== hashAlgorithm) {
|
||||
throw new Error(`Invalid key for this operation, its RSA-PSS parameters do not meet the requirements of "alg" ${algorithm}.`);
|
||||
}
|
||||
|
||||
if (saltLength !== undefined && saltLength > length >> 3) {
|
||||
throw new Error(`Invalid key for this operation, its RSA-PSS parameter saltLength does not meet the requirements of "alg" ${algorithm}.`)
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jsonwebtoken",
|
||||
"version": "8.5.1",
|
||||
"version": "9.0.0",
|
||||
"description": "JSON Web Token implementation (symmetric and asymmetric)",
|
||||
"main": "index.js",
|
||||
"nyc": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const timespan = require('./lib/timespan');
|
||||
const PS_SUPPORTED = require('./lib/psSupported');
|
||||
const validateAsymmetricKey = require('./lib/validateAsymmetricKey');
|
||||
const jws = require('jws');
|
||||
const {includes, isBoolean, isInteger, isNumber, isPlainObject, isString, once} = require('lodash')
|
||||
const { KeyObject, createSecretKey, createPrivateKey } = require('crypto')
|
||||
@@ -22,7 +23,8 @@ const sign_options_schema = {
|
||||
noTimestamp: { isValid: isBoolean, message: '"noTimestamp" must be a boolean' },
|
||||
keyid: { isValid: isString, message: '"keyid" must be a string' },
|
||||
mutatePayload: { isValid: isBoolean, message: '"mutatePayload" must be a boolean' },
|
||||
allowInsecureKeySizes: { isValid: isBoolean, message: '"allowInsecureKeySizes" must be a boolean'}
|
||||
allowInsecureKeySizes: { isValid: isBoolean, message: '"allowInsecureKeySizes" must be a boolean'},
|
||||
allowInvalidAsymmetricKeyTypes: { isValid: isBoolean, message: '"allowInvalidAsymmetricKeyTypes" must be a boolean'}
|
||||
};
|
||||
|
||||
const registered_claims_schema = {
|
||||
@@ -166,6 +168,14 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
|
||||
return failure(error);
|
||||
}
|
||||
|
||||
if (!options.allowInvalidAsymmetricKeyTypes) {
|
||||
try {
|
||||
validateAsymmetricKey(header.alg, secretOrPrivateKey);
|
||||
} catch (error) {
|
||||
return failure(error);
|
||||
}
|
||||
}
|
||||
|
||||
const timestamp = payload.iat || Math.floor(Date.now() / 1000);
|
||||
|
||||
if (options.noTimestamp) {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
-----BEGIN DSA PRIVATE KEY-----
|
||||
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
|
||||
-----END DSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,36 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
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
|
||||
y0elWU3mxe6Rn6R7WHM8or7sX3tWDq6OntJlKg==
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -1,17 +1,17 @@
|
||||
var jwt = require('../index');
|
||||
var PS_SUPPORTED = require('../lib/psSupported');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
const jwt = require('../index');
|
||||
const PS_SUPPORTED = require('../lib/psSupported');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
var expect = require('chai').expect;
|
||||
var assert = require('chai').assert;
|
||||
var ms = require('ms');
|
||||
const expect = require('chai').expect;
|
||||
const assert = require('chai').assert;
|
||||
const ms = require('ms');
|
||||
|
||||
function loadKey(filename) {
|
||||
return fs.readFileSync(path.join(__dirname, filename));
|
||||
}
|
||||
|
||||
var algorithms = {
|
||||
const algorithms = {
|
||||
RS256: {
|
||||
pub_key: loadKey('pub.pem'),
|
||||
priv_key: loadKey('priv.pem'),
|
||||
@@ -35,18 +35,17 @@ if (PS_SUPPORTED) {
|
||||
}
|
||||
|
||||
|
||||
describe('Asymmetric Algorithms', function(){
|
||||
|
||||
describe('Asymmetric Algorithms', function() {
|
||||
Object.keys(algorithms).forEach(function (algorithm) {
|
||||
describe(algorithm, function () {
|
||||
var pub = algorithms[algorithm].pub_key;
|
||||
var priv = algorithms[algorithm].priv_key;
|
||||
const pub = algorithms[algorithm].pub_key;
|
||||
const priv = algorithms[algorithm].priv_key;
|
||||
|
||||
// "invalid" means it is not the public key for the loaded "priv" key
|
||||
var invalid_pub = algorithms[algorithm].invalid_pub_key;
|
||||
const invalid_pub = algorithms[algorithm].invalid_pub_key;
|
||||
|
||||
describe('when signing a token', function () {
|
||||
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm });
|
||||
const token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm });
|
||||
|
||||
it('should be syntactically valid', function () {
|
||||
expect(token).to.be.a('string');
|
||||
@@ -73,13 +72,13 @@ describe('Asymmetric Algorithms', function(){
|
||||
|
||||
context('synchronous', function () {
|
||||
it('should validate with public key', function () {
|
||||
var decoded = jwt.verify(token, pub);
|
||||
const decoded = jwt.verify(token, pub);
|
||||
assert.ok(decoded.foo);
|
||||
assert.equal('bar', decoded.foo);
|
||||
});
|
||||
|
||||
it('should throw with invalid public key', function () {
|
||||
var jwtVerify = jwt.verify.bind(null, token, invalid_pub)
|
||||
const jwtVerify = jwt.verify.bind(null, token, invalid_pub)
|
||||
assert.throw(jwtVerify, 'invalid signature');
|
||||
});
|
||||
});
|
||||
@@ -87,9 +86,8 @@ describe('Asymmetric Algorithms', function(){
|
||||
});
|
||||
|
||||
describe('when signing a token with expiration', function () {
|
||||
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, expiresIn: '10m' });
|
||||
|
||||
it('should be valid expiration', function (done) {
|
||||
const token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, expiresIn: '10m' });
|
||||
jwt.verify(token, pub, function (err, decoded) {
|
||||
assert.isNotNull(decoded);
|
||||
assert.isNull(err);
|
||||
@@ -99,8 +97,7 @@ describe('Asymmetric Algorithms', function(){
|
||||
|
||||
it('should be invalid', function (done) {
|
||||
// expired token
|
||||
token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, expiresIn: -1 * ms('10m') });
|
||||
|
||||
const token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, expiresIn: -1 * ms('10m') });
|
||||
jwt.verify(token, pub, function (err, decoded) {
|
||||
assert.isUndefined(decoded);
|
||||
assert.isNotNull(err);
|
||||
@@ -113,7 +110,7 @@ describe('Asymmetric Algorithms', function(){
|
||||
|
||||
it('should NOT be invalid', function (done) {
|
||||
// expired token
|
||||
token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, expiresIn: -1 * ms('10m') });
|
||||
const token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, expiresIn: -1 * ms('10m') });
|
||||
|
||||
jwt.verify(token, pub, { ignoreExpiration: true }, function (err, decoded) {
|
||||
assert.ok(decoded.foo);
|
||||
@@ -135,7 +132,7 @@ describe('Asymmetric Algorithms', function(){
|
||||
});
|
||||
|
||||
describe('when decoding a jwt token with additional parts', function () {
|
||||
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm });
|
||||
const token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm });
|
||||
|
||||
it('should throw', function (done) {
|
||||
jwt.verify(token + '.foo', pub, function (err, decoded) {
|
||||
@@ -148,7 +145,7 @@ describe('Asymmetric Algorithms', function(){
|
||||
|
||||
describe('when decoding a invalid jwt token', function () {
|
||||
it('should return null', function (done) {
|
||||
var payload = jwt.decode('whatever.token');
|
||||
const payload = jwt.decode('whatever.token');
|
||||
assert.isNull(payload);
|
||||
done();
|
||||
});
|
||||
@@ -156,16 +153,16 @@ describe('Asymmetric Algorithms', function(){
|
||||
|
||||
describe('when decoding a valid jwt token', function () {
|
||||
it('should return the payload', function (done) {
|
||||
var obj = { foo: 'bar' };
|
||||
var token = jwt.sign(obj, priv, { algorithm: algorithm });
|
||||
var payload = jwt.decode(token);
|
||||
const obj = { foo: 'bar' };
|
||||
const token = jwt.sign(obj, priv, { algorithm: algorithm });
|
||||
const payload = jwt.decode(token);
|
||||
assert.equal(payload.foo, obj.foo);
|
||||
done();
|
||||
});
|
||||
it('should return the header and payload and signature if complete option is set', function (done) {
|
||||
var obj = { foo: 'bar' };
|
||||
var token = jwt.sign(obj, priv, { algorithm: algorithm });
|
||||
var decoded = jwt.decode(token, { complete: true });
|
||||
const obj = { foo: 'bar' };
|
||||
const token = jwt.sign(obj, priv, { algorithm: algorithm });
|
||||
const decoded = jwt.decode(token, { complete: true });
|
||||
assert.equal(decoded.payload.foo, obj.foo);
|
||||
assert.deepEqual(decoded.header, { typ: 'JWT', alg: algorithm });
|
||||
assert.ok(typeof decoded.signature == 'string');
|
||||
@@ -174,4 +171,38 @@ describe('Asymmetric Algorithms', function(){
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when signing a token with an unsupported private key type', function () {
|
||||
it('should throw an error', function() {
|
||||
const obj = { foo: 'bar' };
|
||||
const key = loadKey('dsa-private.pem');
|
||||
const algorithm = 'RS256';
|
||||
|
||||
expect(function() {
|
||||
jwt.sign(obj, key, { algorithm });
|
||||
}).to.throw('Unknown key type "dsa".');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when signing a token with an incorrect private key type', function () {
|
||||
it('should throw a validation error if key validation is enabled', function() {
|
||||
const obj = { foo: 'bar' };
|
||||
const key = loadKey('rsa-private.pem');
|
||||
const algorithm = 'ES256';
|
||||
|
||||
expect(function() {
|
||||
jwt.sign(obj, key, { algorithm });
|
||||
}).to.throw(/"alg" parameter for "rsa" key type must be one of:/);
|
||||
});
|
||||
|
||||
it('should throw an unknown error if key validation is disabled', function() {
|
||||
const obj = { foo: 'bar' };
|
||||
const key = loadKey('rsa-private.pem');
|
||||
const algorithm = 'ES256';
|
||||
|
||||
expect(function() {
|
||||
jwt.sign(obj, key, { algorithm, allowInvalidAsymmetricKeyTypes: true });
|
||||
}).to.not.throw(/"alg" parameter for "rsa" key type must be one of:/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIMP1Xt/ic2jAHJva2Pll866d1jYL+dk3VdLytEU1+LFmoAoGCCqGSM49
|
||||
AwEHoUQDQgAEvIywoA1H1a2XpPPTqsRxSk6YnNRVsu4E+wTvb7uV6Yttvko9zWar
|
||||
jmtM3LHDXk/nHn+Pva0KD+lby8gb2daHGg==
|
||||
-----END EC PRIVATE KEY-----
|
||||
@@ -0,0 +1,29 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIE8gIBADBCBgkqhkiG9w0BAQowNaAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZI
|
||||
hvcNAQEIMA0GCWCGSAFlAwQCAQUAogQCAgQABIIEpzCCBKMCAQACggEBAJy3FuDR
|
||||
1qKXsC8o+0xDJbuJCnysT71EFDGQY2/b3cZmxW3rzDYLyE65t2Go1jeK5Kxs+kwS
|
||||
1VxfefD8DifeDZN66wjRse4iWLcxmQB5FfishXOdozciimgXNvXJNS8X//feSofl
|
||||
vDQaTUI0NJnw1qQ2CB0pgGInwajsRKpWnDOhfk3NA/cmGlmfhTtDSTxq0ReytUie
|
||||
TjY7gy+S9YYm4bAgBcMeoup0GEPzYccK4+1yCmWzQZGFcrY1cuB9bL+vT7ajQFhe
|
||||
WVKlp6z35GyBF2zI7gJSkHpUHaWV5+Z9aTr6+YP6U7xuCRvXQ/l6BEOUjt4Es2YG
|
||||
3frgxeVbOs1gAakCAwEAAQKCAQAMvFxhnOwCfq1Ux9HUWsigOvzdMOuyB+xUMtXB
|
||||
625Uh1mYG0eXRNHcg/9BMoVmMiVvVdPphsZMIX45dWJ5HvSffafIKbJ6FdR73s3+
|
||||
WdjNQsf9o1v2SRpSZ0CSLO3ji+HDdQ89iBAJc/G/ZZq4v/fRlIqIRC0ozO5SGhFi
|
||||
fnNnRqH78d2KeJMX/g9jBZM8rJQCi+pb0keHmFmLJ5gZa4HokE8rWQJQY46PVYUH
|
||||
W2BwEJToMl3MPC7D95soWVuFt3KHnIWhuma/tnCmd2AUvcMrdWq0CwStH3vuX4LB
|
||||
vJug0toWkobt1tzZgzzCASb2EpzJj8UNxP1CzTQWsvl8OephAoGBAMVnmZeLHoh2
|
||||
kxn/+rXetZ4Msjgu19MHNQAtlMvqzwZLan0K/BhnHprJLy4SDOuQYIs+PYJuXdT7
|
||||
Yv2mp9kwTPz8glP9LAto4MDeDfCu0cyXmZb2VQcT/lqVyrwfx3Psqxm/Yxg62YKr
|
||||
aQE8WqgZGUdOvU9dYU+7EmPlYpdGpPVlAoGBAMs7ks+12oE6kci3WApdnt0kk5+f
|
||||
8fbQ0lp2vR3tEw8DURa5FnHWA4o46XvcMcuXwZBrpxANPNAxJJjMBs1hSkc8h4hd
|
||||
4vjtRNYJpj+uBdDIRmdqTzbpWv+hv8Xpiol5EVgnMVs2UZWDjoxQ+mYa1R8tAUfj
|
||||
ojzV2KBMWGCoHgj1AoGALki6JGQEBq72kpQILnhHUQVdC/s/s0TvUlldl+o4HBu2
|
||||
nhbjQL182YHuQ/kLenfhiwRO27QQ4A0JCrv2gt/mTTLPQ+4KU6qFd/MYhaQXoMay
|
||||
xkh/aydu7cJNRIqW80E8ZM8Q5u91bEPQXO/PubYYzTVTAba9SDpud2mjEiEIMFkC
|
||||
gYEAxINEQEgtkkuZ76UpIkzIcjkN7YlxJCFjZUnvL+KvTRL986TgyQ4RujOxwKx4
|
||||
Ec8ZwZX2opTKOt1p771IzorGkf87ZmayM9TpfLUz5dtVkD43pYOsOQKHlStIDgz2
|
||||
gltoo/6xwOrTFGlzCsa6eMR1U4Hm/SZlF8IHh2iLBFtLP4kCgYBqTi1XeWeVQVSA
|
||||
y9Wolv9kMoRh/Xh6F2D8bTTybGshDVO+P4YLM4lLxh5UDZAd/VOkdf3ZIcUGv022
|
||||
lxrYbLbIEGckMCpkdHeZH/1/iuJUeiCrXeyNlQsXBrmJKr/0lENniJHGpiSEyvY5
|
||||
D8Oafyjd7ZjUmyBFvS4heQEC6Pjo3Q==
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,29 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIE8QIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZI
|
||||
hvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAEggSnMIIEowIBAAKCAQEA00tEqqyF
|
||||
VnyvcVA2ewVoSicCMdQXmWyYM82sBWX0wcnn0WUuZp1zjux4xTvQ71Lhx95OJCQZ
|
||||
7r7b2192Im5ca37wNRbI6DhyXNdNVFXLFYlNAvgP+V0gIwlr6NgopdJqHCjYVv/g
|
||||
GOoesRZaDdtV1A3O9CXdJ34x2HZh7nhwYK5hqZDhUW4rd+5GzIIzwCJfwgTQpkIc
|
||||
18UeMMEoKJ6A0ixdpf43HqJ5fAB5nsbYFhyHpfiX1UO2EFJtSdbKEIbRmqcbNjG1
|
||||
tu1tjt6u8LI2coetLh/IYMbMfkyQz+eAUHLQCUb2R8BqLOL3hRqEsVTBo93UJlOs
|
||||
VWC1fKaq+HOEWQIDAQABAoIBAAet23PagPQTjwAZcAlzjlvs5AMHQsj5gznqwSmR
|
||||
ut3/e7SGrrOIXbv1iIQejZQ3w8CS/0MH/ttIRiRIaWTh9EDsjvKsU9FAxUNDiJTG
|
||||
k3LCbTFCQ7kGiJWiu4XDCWMmwmLTRzLjlMjtr/+JS5eSVPcNKMGDI3D9K0xDLSxQ
|
||||
u0DVigYgWOCWlejHCEU4yi6vBO0HlumWjVPelWb9GmihBDwCLUJtG0JA6H6rw+KS
|
||||
i6SNXcMGVKfjEghChRp+HaMvLvMgU44Ptnj8jhlfBctXInBY1is1FfDSWxXdVbUM
|
||||
1HdKXfV4A50GXSvJLiWP9ZZsaZ7NiBJK8IiJBXD72EFOzwECgYEA3RjnTJn9emzG
|
||||
84eIHZQujWWt4Tk/wjeLJYOYtAZpF7R3/fYLVypX9Bsw1IbwZodq/jChTjMaUkYt
|
||||
//FgUjF/t0uakEg1i+THPZvktNB8Q1E9NwHerB8HF/AD/jMALD+ejdLQ11Z4VScw
|
||||
zyNmSvD9I84/sgpms5YVKSH9sqww2RkCgYEA9KYws3sTfRLc1hlsS25V6+Zg3ZCk
|
||||
iGcp+zrxGC1gb2/PpRvEDBucZO21KbSRuQDavWIOZYl4fGu7s8wo2oF8RxOsHQsM
|
||||
LJyjklruvtjnvuoft/bGAv2zLQkNaj+f7IgK6965gIxcLYL66UPCZZkTfL5CoJis
|
||||
V0v2hBh1ES5bLUECgYEAuONeaLxNL9dO989akAGefDePFExfePYhshk91S2XLG+J
|
||||
+CGMkjOioUsrpk3BMrwDSNU5zr8FP8/YH7OlrJYgCxN6CTWZMYb65hY7RskhYNnK
|
||||
qvkxUBYSRH49mJDlkBsTZ93nLmvs7Kh9NHqRzBGCXjLXKPdxsrPKtj7qfENqBeEC
|
||||
gYAC9dPXCCE3PTgw2wPlccNWZGY9qBdlkyH96TurmDj3gDnZ/JkFsHvW+M1dYNL2
|
||||
kx0Sd5JHBj/P+Zm+1jSUWEbBsWo+u7h8/bQ4/CKxanx7YefaWQESXjGB1P81jumH
|
||||
einvqrVB6fDfmBsjIW/DvPNwafjyaoaDU+b6uDUKbS4rQQKBgCe0pvDl5lO8FM81
|
||||
NP7GoCIu1gKBS+us1sgYE65ZFmVXJ6b5DckvobXSjM60G2N5w2xaXEXJsnwMApf1
|
||||
SClQUsgNWcSXRwL+w0pIdyFKS25BSfwUNQ9n7QLJcYgmflbARTfB3He/10vbFzTp
|
||||
G6ZAiKUp9bKFPzviII40AEPL2hPX
|
||||
-----END PRIVATE KEY-----
|
||||
+15
-15
@@ -6,31 +6,31 @@ var PS_SUPPORTED = require('../lib/psSupported');
|
||||
describe('schema', function() {
|
||||
|
||||
describe('sign options', function() {
|
||||
|
||||
var cert_rsa_priv = fs.readFileSync(__dirname + '/rsa-private.pem');
|
||||
var cert_ecdsa_priv = fs.readFileSync(__dirname + '/ecdsa-private.pem');
|
||||
var cert_secp384r1_priv = fs.readFileSync(__dirname + '/secp384r1-private.pem');
|
||||
var cert_secp521r1_priv = fs.readFileSync(__dirname + '/secp521r1-private.pem');
|
||||
|
||||
function sign(options, secret) {
|
||||
var isEcdsa = options.algorithm && options.algorithm.indexOf('ES') === 0;
|
||||
jwt.sign({foo: 123}, secret || (isEcdsa ? cert_ecdsa_priv : cert_rsa_priv), options);
|
||||
function sign(options, secretOrPrivateKey) {
|
||||
jwt.sign({foo: 123}, secretOrPrivateKey, options);
|
||||
}
|
||||
|
||||
it('should validate algorithm', function () {
|
||||
expect(function () {
|
||||
sign({ algorithm: 'foo' });
|
||||
sign({ algorithm: 'foo' }, cert_rsa_priv);
|
||||
}).to.throw(/"algorithm" must be a valid string enum value/);
|
||||
sign({ algorithm: 'none' });
|
||||
sign({algorithm: 'RS256'});
|
||||
sign({algorithm: 'RS384'});
|
||||
sign({algorithm: 'RS512'});
|
||||
sign({ algorithm: 'none' }, null);
|
||||
sign({algorithm: 'RS256'}, cert_rsa_priv);
|
||||
sign({algorithm: 'RS384'}, cert_rsa_priv);
|
||||
sign({algorithm: 'RS512'}, cert_rsa_priv);
|
||||
if (PS_SUPPORTED) {
|
||||
sign({algorithm: 'PS256'});
|
||||
sign({algorithm: 'PS384'});
|
||||
sign({algorithm: 'PS512'});
|
||||
sign({algorithm: 'PS256'}, cert_rsa_priv);
|
||||
sign({algorithm: 'PS384'}, cert_rsa_priv);
|
||||
sign({algorithm: 'PS512'}, cert_rsa_priv);
|
||||
}
|
||||
sign({algorithm: 'ES256'});
|
||||
sign({algorithm: 'ES384'});
|
||||
sign({algorithm: 'ES512'});
|
||||
sign({algorithm: 'ES256'}, cert_ecdsa_priv);
|
||||
sign({algorithm: 'ES384'}, cert_secp384r1_priv);
|
||||
sign({algorithm: 'ES512'}, cert_secp521r1_priv);
|
||||
sign({algorithm: 'HS256'}, 'superSecret');
|
||||
sign({algorithm: 'HS384'}, 'superSecret');
|
||||
sign({algorithm: 'HS512'}, 'superSecret');
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIGkAgEBBDCez58vZHVp+ArI7/fe835GAtRzE0AtrxGgQAY1U/uk2SQOaSw1ph61
|
||||
3Unr0ygS172gBwYFK4EEACKhZANiAARtwlnIqYqZxfiWR+/EM35nKHuLpOjUHiX1
|
||||
kEpSS03C9XlrBLNwLQfgjpYx9Qvqh26XAzTe74DYjcc748R+zZD2YAd3lV+OcdRE
|
||||
U+DWm4j5E6dlOXzvmw/3qxUcg3rRgR4=
|
||||
-----END EC PRIVATE KEY-----
|
||||
@@ -0,0 +1,7 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIHcAgEBBEIBlWXKBKKCgTgf7+NS09TMv7/NO3RtMBn9xTe+46oNNNK405lrZ9mz
|
||||
WYtlsYvkdsc2Cx3v5V8JegaCOM+XtAZ0MNKgBwYFK4EEACOhgYkDgYYABAFNzaM7
|
||||
Zb9ug0p5KaZb5mjHrIshoVJSHaOXGtcjLVUakYVk0v9VsE+FKqyuLYcORUuAZdxl
|
||||
ITAlC5e5JZ0o8NEKbAE+8oOrePrItR3IFBtWO15p7qiRa2dBB8oQklFrmQaJYn4K
|
||||
fDV0hYpfu6ahpRNu2akR7aMXL/vXrptCH/n64q9KjA==
|
||||
-----END EC PRIVATE KEY-----
|
||||
@@ -0,0 +1,142 @@
|
||||
const validateAsymmetricKey = require('../lib/validateAsymmetricKey');
|
||||
const PS_SUPPORTED = require('../lib/psSupported');
|
||||
const ASYMMETRIC_KEY_DETAILS_SUPPORTED = require('../lib/asymmetricKeyDetailsSupported');
|
||||
const RSA_PSS_KEY_DETAILS_SUPPORTED = require('../lib/rsaPssKeyDetailsSupported');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { createPrivateKey } = require('crypto');
|
||||
const expect = require('chai').expect;
|
||||
|
||||
function loadKey(filename) {
|
||||
return createPrivateKey(
|
||||
fs.readFileSync(path.join(__dirname, filename))
|
||||
);
|
||||
}
|
||||
|
||||
const algorithmParams = {
|
||||
RS256: {
|
||||
invalidPrivateKey: loadKey('secp384r1-private.pem')
|
||||
},
|
||||
ES256: {
|
||||
invalidPrivateKey: loadKey('priv.pem')
|
||||
}
|
||||
};
|
||||
|
||||
if (PS_SUPPORTED) {
|
||||
algorithmParams.PS256 = {
|
||||
invalidPrivateKey: loadKey('secp384r1-private.pem')
|
||||
};
|
||||
}
|
||||
|
||||
describe('Asymmetric key validation', function() {
|
||||
Object.keys(algorithmParams).forEach(function(algorithm) {
|
||||
describe(algorithm, function() {
|
||||
const keys = algorithmParams[algorithm];
|
||||
|
||||
describe('when validating a key with an invalid private key type', function () {
|
||||
it('should throw an error', function () {
|
||||
const expectedErrorMessage = /"alg" parameter for "[\w\d-]+" key type must be one of:/;
|
||||
|
||||
expect(function() {
|
||||
validateAsymmetricKey(algorithm, keys.invalidPrivateKey);
|
||||
}).to.throw(expectedErrorMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the function has missing parameters', function() {
|
||||
it('should pass the validation if no key has been provided', function() {
|
||||
const algorithm = 'ES256';
|
||||
validateAsymmetricKey(algorithm);
|
||||
});
|
||||
|
||||
it('should pass the validation if no algorithm has been provided', function() {
|
||||
const key = loadKey('dsa-private.pem');
|
||||
validateAsymmetricKey(null, key);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when validating a key with an unsupported type', function () {
|
||||
it('should throw an error', function() {
|
||||
const algorithm = 'RS256';
|
||||
const key = loadKey('dsa-private.pem');
|
||||
const expectedErrorMessage = 'Unknown key type "dsa".';
|
||||
|
||||
expect(function() {
|
||||
validateAsymmetricKey(algorithm, key);
|
||||
}).to.throw(expectedErrorMessage);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Elliptic curve algorithms', function () {
|
||||
const curvesAlgorithms = [
|
||||
{ algorithm: 'ES256', curve: 'prime256v1' },
|
||||
{ algorithm: 'ES384', curve: 'secp384r1' },
|
||||
{ algorithm: 'ES512', curve: 'secp521r1' },
|
||||
];
|
||||
|
||||
const curvesKeys = [
|
||||
{ curve: 'prime256v1', key: loadKey('prime256v1-private.pem') },
|
||||
{ curve: 'secp384r1', key: loadKey('secp384r1-private.pem') },
|
||||
{ curve: 'secp521r1', key: loadKey('secp521r1-private.pem') }
|
||||
];
|
||||
|
||||
describe('when validating keys generated using Elliptic Curves', function () {
|
||||
curvesAlgorithms.forEach(function(curveAlgorithm) {
|
||||
curvesKeys
|
||||
.forEach((curveKeys) => {
|
||||
if (curveKeys.curve !== curveAlgorithm.curve) {
|
||||
if (ASYMMETRIC_KEY_DETAILS_SUPPORTED) {
|
||||
it(`should throw an error when validating an ${curveAlgorithm.algorithm} token for key with curve ${curveKeys.curve}`, function() {
|
||||
expect(() => {
|
||||
validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key);
|
||||
}).to.throw(`"alg" parameter "${curveAlgorithm.algorithm}" requires curve "${curveAlgorithm.curve}".`);
|
||||
});
|
||||
} else {
|
||||
it(`should pass the validation for incorrect keys if the Node version does not support checking the key's curve name`, function() {
|
||||
expect(() => {
|
||||
validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key);
|
||||
}).not.to.throw();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
it(`should accept an ${curveAlgorithm.algorithm} token for key with curve ${curveKeys.curve}`, function() {
|
||||
expect(() => {
|
||||
validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key);
|
||||
}).not.to.throw();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (RSA_PSS_KEY_DETAILS_SUPPORTED) {
|
||||
describe('RSA-PSS algorithms', function () {
|
||||
const key = loadKey('rsa-pss-private.pem');
|
||||
|
||||
it(`it should throw an error when validating a key with wrong RSA-RSS parameters`, function () {
|
||||
const algorithm = 'PS512';
|
||||
expect(function() {
|
||||
validateAsymmetricKey(algorithm, key);
|
||||
}).to.throw('Invalid key for this operation, its RSA-PSS parameters do not meet the requirements of "alg" PS512')
|
||||
});
|
||||
|
||||
it(`it should throw an error when validating a key with invalid salt length`, function () {
|
||||
const algorithm = 'PS256';
|
||||
const shortSaltKey = loadKey('rsa-pss-invalid-salt-length-private.pem');
|
||||
expect(function() {
|
||||
validateAsymmetricKey(algorithm, shortSaltKey);
|
||||
}).to.throw('Invalid key for this operation, its RSA-PSS parameter saltLength does not meet the requirements of "alg" PS256.')
|
||||
});
|
||||
|
||||
it(`it should pass the validation when the key matches all the requirements for the algorithm`, function () {
|
||||
expect(function() {
|
||||
const algorithm = 'PS256';
|
||||
validateAsymmetricKey(algorithm, key);
|
||||
}).not.to.throw()
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
+74
-43
@@ -1,22 +1,22 @@
|
||||
var jwt = require('../index');
|
||||
var jws = require('jws');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var sinon = require('sinon');
|
||||
var JsonWebTokenError = require('../lib/JsonWebTokenError');
|
||||
const jwt = require('../index');
|
||||
const jws = require('jws');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const sinon = require('sinon');
|
||||
const JsonWebTokenError = require('../lib/JsonWebTokenError');
|
||||
|
||||
var assert = require('chai').assert;
|
||||
var expect = require('chai').expect;
|
||||
const assert = require('chai').assert;
|
||||
const expect = require('chai').expect;
|
||||
|
||||
describe('verify', function() {
|
||||
var pub = fs.readFileSync(path.join(__dirname, 'pub.pem'));
|
||||
var priv = fs.readFileSync(path.join(__dirname, 'priv.pem'));
|
||||
const pub = fs.readFileSync(path.join(__dirname, 'pub.pem'));
|
||||
const priv = fs.readFileSync(path.join(__dirname, 'priv.pem'));
|
||||
|
||||
it('should first assume JSON claim set', function (done) {
|
||||
var header = { alg: 'RS256' };
|
||||
var payload = { iat: Math.floor(Date.now() / 1000 ) };
|
||||
const header = { alg: 'RS256' };
|
||||
const payload = { iat: Math.floor(Date.now() / 1000 ) };
|
||||
|
||||
var signed = jws.sign({
|
||||
const signed = jws.sign({
|
||||
header: header,
|
||||
payload: payload,
|
||||
secret: priv,
|
||||
@@ -31,10 +31,10 @@ describe('verify', function() {
|
||||
});
|
||||
|
||||
it('should not be able to verify unsigned token', function () {
|
||||
var header = { alg: 'none' };
|
||||
var payload = { iat: Math.floor(Date.now() / 1000 ) };
|
||||
const header = { alg: 'none' };
|
||||
const payload = { iat: Math.floor(Date.now() / 1000 ) };
|
||||
|
||||
var signed = jws.sign({
|
||||
const signed = jws.sign({
|
||||
header: header,
|
||||
payload: payload,
|
||||
secret: 'secret',
|
||||
@@ -47,10 +47,10 @@ describe('verify', function() {
|
||||
});
|
||||
|
||||
it('should not be able to verify unsigned token', function () {
|
||||
var header = { alg: 'none' };
|
||||
var payload = { iat: Math.floor(Date.now() / 1000 ) };
|
||||
const header = { alg: 'none' };
|
||||
const payload = { iat: Math.floor(Date.now() / 1000 ) };
|
||||
|
||||
var signed = jws.sign({
|
||||
const signed = jws.sign({
|
||||
header: header,
|
||||
payload: payload,
|
||||
secret: 'secret',
|
||||
@@ -63,10 +63,10 @@ describe('verify', function() {
|
||||
});
|
||||
|
||||
it('should be able to verify unsigned token when none is specified', function (done) {
|
||||
var header = { alg: 'none' };
|
||||
var payload = { iat: Math.floor(Date.now() / 1000 ) };
|
||||
const header = { alg: 'none' };
|
||||
const payload = { iat: Math.floor(Date.now() / 1000 ) };
|
||||
|
||||
var signed = jws.sign({
|
||||
const signed = jws.sign({
|
||||
header: header,
|
||||
payload: payload,
|
||||
secret: 'secret',
|
||||
@@ -99,11 +99,11 @@ describe('verify', function() {
|
||||
});
|
||||
|
||||
describe('secret or token as callback', function () {
|
||||
var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU';
|
||||
var key = 'key';
|
||||
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU';
|
||||
const key = 'key';
|
||||
|
||||
var payload = { foo: 'bar', iat: 1437018582, exp: 1437018592 };
|
||||
var options = {algorithms: ['HS256'], ignoreExpiration: true};
|
||||
const payload = { foo: 'bar', iat: 1437018582, exp: 1437018592 };
|
||||
const options = {algorithms: ['HS256'], ignoreExpiration: true};
|
||||
|
||||
it('without callback', function (done) {
|
||||
jwt.verify(token, key, options, function (err, p) {
|
||||
@@ -114,7 +114,7 @@ describe('verify', function() {
|
||||
});
|
||||
|
||||
it('simple callback', function (done) {
|
||||
var keyFunc = function(header, callback) {
|
||||
const keyFunc = function(header, callback) {
|
||||
assert.deepEqual(header, { alg: 'HS256', typ: 'JWT' });
|
||||
|
||||
callback(undefined, key);
|
||||
@@ -128,7 +128,7 @@ describe('verify', function() {
|
||||
});
|
||||
|
||||
it('should error if called synchronously', function (done) {
|
||||
var keyFunc = function(header, callback) {
|
||||
const keyFunc = function(header, callback) {
|
||||
callback(undefined, key);
|
||||
};
|
||||
|
||||
@@ -140,7 +140,7 @@ describe('verify', function() {
|
||||
});
|
||||
|
||||
it('simple error', function (done) {
|
||||
var keyFunc = function(header, callback) {
|
||||
const keyFunc = function(header, callback) {
|
||||
callback(new Error('key not found'));
|
||||
};
|
||||
|
||||
@@ -153,7 +153,7 @@ describe('verify', function() {
|
||||
});
|
||||
|
||||
it('delayed callback', function (done) {
|
||||
var keyFunc = function(header, callback) {
|
||||
const keyFunc = function(header, callback) {
|
||||
setTimeout(function() {
|
||||
callback(undefined, key);
|
||||
}, 25);
|
||||
@@ -167,7 +167,7 @@ describe('verify', function() {
|
||||
});
|
||||
|
||||
it('delayed error', function (done) {
|
||||
var keyFunc = function(header, callback) {
|
||||
const keyFunc = function(header, callback) {
|
||||
setTimeout(function() {
|
||||
callback(new Error('key not found'));
|
||||
}, 25);
|
||||
@@ -184,17 +184,17 @@ describe('verify', function() {
|
||||
|
||||
describe('expiration', function () {
|
||||
// { foo: 'bar', iat: 1437018582, exp: 1437018592 }
|
||||
var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU';
|
||||
var key = 'key';
|
||||
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU';
|
||||
const key = 'key';
|
||||
|
||||
var clock;
|
||||
let clock;
|
||||
afterEach(function () {
|
||||
try { clock.restore(); } catch (e) {}
|
||||
});
|
||||
|
||||
it('should error on expired token', function (done) {
|
||||
clock = sinon.useFakeTimers(1437018650000); // iat + 58s, exp + 48s
|
||||
var options = {algorithms: ['HS256']};
|
||||
const options = {algorithms: ['HS256']};
|
||||
|
||||
jwt.verify(token, key, options, function (err, p) {
|
||||
assert.equal(err.name, 'TokenExpiredError');
|
||||
@@ -208,7 +208,7 @@ describe('verify', function() {
|
||||
|
||||
it('should not error on expired token within clockTolerance interval', function (done) {
|
||||
clock = sinon.useFakeTimers(1437018594000); // iat + 12s, exp + 2s
|
||||
var options = {algorithms: ['HS256'], clockTolerance: 5 }
|
||||
const options = {algorithms: ['HS256'], clockTolerance: 5 }
|
||||
|
||||
jwt.verify(token, key, options, function (err, p) {
|
||||
assert.isNull(err);
|
||||
@@ -218,16 +218,16 @@ describe('verify', function() {
|
||||
});
|
||||
|
||||
describe('option: clockTimestamp', function () {
|
||||
var clockTimestamp = 1000000000;
|
||||
const clockTimestamp = 1000000000;
|
||||
it('should verify unexpired token relative to user-provided clockTimestamp', function (done) {
|
||||
var token = jwt.sign({foo: 'bar', iat: clockTimestamp, exp: clockTimestamp + 1}, key);
|
||||
const token = jwt.sign({foo: 'bar', iat: clockTimestamp, exp: clockTimestamp + 1}, key);
|
||||
jwt.verify(token, key, {clockTimestamp: clockTimestamp}, function (err) {
|
||||
assert.isNull(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should error on expired token relative to user-provided clockTimestamp', function (done) {
|
||||
var token = jwt.sign({foo: 'bar', iat: clockTimestamp, exp: clockTimestamp + 1}, key);
|
||||
const token = jwt.sign({foo: 'bar', iat: clockTimestamp, exp: clockTimestamp + 1}, key);
|
||||
jwt.verify(token, key, {clockTimestamp: clockTimestamp + 1}, function (err, p) {
|
||||
assert.equal(err.name, 'TokenExpiredError');
|
||||
assert.equal(err.message, 'jwt expired');
|
||||
@@ -238,7 +238,7 @@ describe('verify', function() {
|
||||
});
|
||||
});
|
||||
it('should verify clockTimestamp is a number', function (done) {
|
||||
var token = jwt.sign({foo: 'bar', iat: clockTimestamp, exp: clockTimestamp + 1}, key);
|
||||
const token = jwt.sign({foo: 'bar', iat: clockTimestamp, exp: clockTimestamp + 1}, key);
|
||||
jwt.verify(token, key, {clockTimestamp: 'notANumber'}, function (err, p) {
|
||||
assert.equal(err.name, 'JsonWebTokenError');
|
||||
assert.equal(err.message,'clockTimestamp must be a number');
|
||||
@@ -250,10 +250,10 @@ describe('verify', function() {
|
||||
|
||||
describe('option: maxAge and clockTimestamp', function () {
|
||||
// { foo: 'bar', iat: 1437018582, exp: 1437018800 } exp = iat + 218s
|
||||
var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODgwMH0.AVOsNC7TiT-XVSpCpkwB1240izzCIJ33Lp07gjnXVpA';
|
||||
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODgwMH0.AVOsNC7TiT-XVSpCpkwB1240izzCIJ33Lp07gjnXVpA';
|
||||
it('cannot be more permissive than expiration', function (done) {
|
||||
var clockTimestamp = 1437018900; // iat + 318s (exp: iat + 218s)
|
||||
var options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, maxAge: '1000y'};
|
||||
const clockTimestamp = 1437018900; // iat + 318s (exp: iat + 218s)
|
||||
const options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, maxAge: '1000y'};
|
||||
|
||||
jwt.verify(token, key, options, function (err, p) {
|
||||
// maxAge not exceded, but still expired
|
||||
@@ -267,4 +267,35 @@ describe('verify', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when verifying a token with an unsupported public key type', function () {
|
||||
it('should throw an error', function() {
|
||||
const token = 'eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2Njk5OTAwMDN9.YdjFWJtPg_9nccMnTfQyesWQ0UX-GsWrfCGit_HqjeIkNjoV6dkAJ8AtbnVEhA4oxwqSXx6ilMOfHEjmMlPtyyyVKkWKQHcIWYnqPbNSEv8a7Men8KhJTIWb4sf5YbhgSCpNvU_VIZjLO1Z0PzzgmEikp0vYbxZFAbCAlZCvUlcIc-kdjIRCnDJe0BBrYRxNLEJtYsf7D1yFIFIqw8-VP87yZdExA4eHsTaE84SgnL24ZK5h5UooDx-IRNd_rrMyio8kNy63grVxCWOtkXZ26iZk6v-HMsnBqxvUwR6-8wfaWrcpADkyUO1q3SNsoTdwtflbvfwgjo3uve0IvIzHMw';
|
||||
const key = fs.readFileSync(path.join(__dirname, 'dsa-public.pem'));
|
||||
|
||||
expect(function() {
|
||||
jwt.verify(token, key);
|
||||
}).to.throw('Unknown key type "dsa".');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when verifying a token with an incorrect public key type', function () {
|
||||
it('should throw a validation error if key validation is enabled', function() {
|
||||
const token = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXkiOiJsb2FkIiwiaWF0IjoxNjcwMjMwNDE2fQ.7TYP8SB_9Tw1fNIfuG60b4tvoLPpDAVBQpV1oepnuKwjUz8GOw4fRLzclo0Q2YAXisJ3zIYMEFsHpYrflfoZJQ';
|
||||
const key = fs.readFileSync(path.join(__dirname, 'rsa-public.pem'));
|
||||
|
||||
expect(function() {
|
||||
jwt.verify(token, key, { algorithms: ['ES256'] });
|
||||
}).to.throw('"alg" parameter for "rsa" key type must be one of: RS256, PS256, RS384, PS384, RS512, PS512.');
|
||||
});
|
||||
|
||||
it('should throw an unknown error if key validation is disabled', function() {
|
||||
const token = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXkiOiJsb2FkIiwiaWF0IjoxNjcwMjMwNDE2fQ.7TYP8SB_9Tw1fNIfuG60b4tvoLPpDAVBQpV1oepnuKwjUz8GOw4fRLzclo0Q2YAXisJ3zIYMEFsHpYrflfoZJQ';
|
||||
const key = fs.readFileSync(path.join(__dirname, 'rsa-public.pem'));
|
||||
|
||||
expect(function() {
|
||||
jwt.verify(token, key, { algorithms: ['ES256'], allowInvalidAsymmetricKeyTypes: true });
|
||||
}).to.not.throw('"alg" parameter for "rsa" key type must be one of: RS256, PS256, RS384, PS384, RS512, PS512.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ const NotBeforeError = require('./lib/NotBeforeError');
|
||||
const TokenExpiredError = require('./lib/TokenExpiredError');
|
||||
const decode = require('./decode');
|
||||
const timespan = require('./lib/timespan');
|
||||
const validateAsymmetricKey = require('./lib/validateAsymmetricKey');
|
||||
const PS_SUPPORTED = require('./lib/psSupported');
|
||||
const jws = require('jws');
|
||||
const {KeyObject, createSecretKey, createPublicKey} = require("crypto");
|
||||
@@ -49,6 +50,10 @@ module.exports = function (jwtString, secretOrPublicKey, options, callback) {
|
||||
return done(new JsonWebTokenError('nonce must be a non-empty string'));
|
||||
}
|
||||
|
||||
if (options.allowInvalidAsymmetricKeyTypes !== undefined && typeof options.allowInvalidAsymmetricKeyTypes !== 'boolean') {
|
||||
return done(new JsonWebTokenError('allowInvalidAsymmetricKeyTypes must be a boolean'));
|
||||
}
|
||||
|
||||
const clockTimestamp = options.clockTimestamp || Math.floor(Date.now() / 1000);
|
||||
|
||||
if (!jwtString){
|
||||
@@ -146,6 +151,14 @@ module.exports = function (jwtString, secretOrPublicKey, options, callback) {
|
||||
return done(new JsonWebTokenError((`secretOrPublicKey must be an asymmetric key when using ${header.alg}`)))
|
||||
}
|
||||
|
||||
if (!options.allowInvalidAsymmetricKeyTypes) {
|
||||
try {
|
||||
validateAsymmetricKey(header.alg, secretOrPublicKey);
|
||||
} catch (e) {
|
||||
return done(e);
|
||||
}
|
||||
}
|
||||
|
||||
let valid;
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user