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:
Edgar Chirivella
2022-12-21 13:36:01 +01:00
committed by GitHub
co-authored by julienwoll
parent ad0184878d
commit 925d95de54
19 changed files with 557 additions and 90 deletions
+18
View File
@@ -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
+3 -1
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
const semver = require('semver');
module.exports = semver.satisfies(process.version, '>=15.7.0');
+3
View File
@@ -0,0 +1,3 @@
const semver = require('semver');
module.exports = semver.satisfies(process.version, '>=16.9.0');
+66
View File
@@ -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
View File
@@ -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": {
+11 -1
View File
@@ -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) {
+36
View File
@@ -0,0 +1,36 @@
-----BEGIN DSA PRIVATE KEY-----
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
-----END DSA PRIVATE KEY-----
+36
View File
@@ -0,0 +1,36 @@
-----BEGIN PUBLIC KEY-----
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
y0elWU3mxe6Rn6R7WHM8or7sX3tWDq6OntJlKg==
-----END PUBLIC KEY-----
+60 -29
View File
@@ -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:/);
});
});
});
+5
View File
@@ -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-----
+29
View File
@@ -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
View File
@@ -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');
+6
View File
@@ -0,0 +1,6 @@
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDCez58vZHVp+ArI7/fe835GAtRzE0AtrxGgQAY1U/uk2SQOaSw1ph61
3Unr0ygS172gBwYFK4EEACKhZANiAARtwlnIqYqZxfiWR+/EM35nKHuLpOjUHiX1
kEpSS03C9XlrBLNwLQfgjpYx9Qvqh26XAzTe74DYjcc748R+zZD2YAd3lV+OcdRE
U+DWm4j5E6dlOXzvmw/3qxUcg3rRgR4=
-----END EC PRIVATE KEY-----
+7
View File
@@ -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-----
+142
View File
@@ -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
View File
@@ -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.');
});
});
});
+13
View File
@@ -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 {