Skip to content

Commit

Permalink
feat: add PS JWA support for applicable node versions (#573)
Browse files Browse the repository at this point in the history
  • Loading branch information
panva authored and ziluvatar committed Feb 20, 2019
1 parent 8737789 commit eefb9d9
Show file tree
Hide file tree
Showing 11 changed files with 86 additions and 12 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
language: node_js
sudo: false
node_js:
- "11"
- "10"
- "9"
- "8"
- "7"
- "6"
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,12 @@ alg Parameter Value | Digital Signature or MAC Algorithm
HS256 | HMAC using SHA-256 hash algorithm
HS384 | HMAC using SHA-384 hash algorithm
HS512 | HMAC using SHA-512 hash algorithm
RS256 | RSASSA using SHA-256 hash algorithm
RS384 | RSASSA using SHA-384 hash algorithm
RS512 | RSASSA using SHA-512 hash algorithm
RS256 | RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm
RS384 | RSASSA-PKCS1-v1_5 using SHA-384 hash algorithm
RS512 | RSASSA-PKCS1-v1_5 using SHA-512 hash algorithm
PS256 | RSASSA-PSS using SHA-256 hash algorithm (only node ^6.12.0 || >=8.0.0)
PS384 | RSASSA-PSS using SHA-384 hash algorithm (only node ^6.12.0 || >=8.0.0)
PS512 | RSASSA-PSS using SHA-512 hash algorithm (only node ^6.12.0 || >=8.0.0)
ES256 | ECDSA using P-256 curve and SHA-256 hash algorithm
ES384 | ECDSA using P-384 curve and SHA-384 hash algorithm
ES512 | ECDSA using P-521 curve and SHA-512 hash algorithm
Expand Down
3 changes: 3 additions & 0 deletions lib/psSupported.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var semver = require('semver');

module.exports = semver.satisfies(process.version, '^6.12.0 || >=8.0.0');
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,16 @@
"url": "https://github.com/auth0/node-jsonwebtoken/issues"
},
"dependencies": {
"jws": "^3.1.5",
"jws": "^3.2.1",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1"
"ms": "^2.1.1",
"semver": "^5.6.0"
},
"devDependencies": {
"atob": "^2.1.2",
Expand Down
8 changes: 7 additions & 1 deletion sign.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var timespan = require('./lib/timespan');
var PS_SUPPORTED = require('./lib/psSupported');
var jws = require('jws');
var includes = require('lodash.includes');
var isBoolean = require('lodash.isboolean');
Expand All @@ -8,11 +9,16 @@ var isPlainObject = require('lodash.isplainobject');
var isString = require('lodash.isstring');
var once = require('lodash.once');

var SUPPORTED_ALGS = ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'none']
if (PS_SUPPORTED) {
SUPPORTED_ALGS.splice(3, 0, 'PS256', 'PS384', 'PS512');
}

var sign_options_schema = {
expiresIn: { isValid: function(value) { return isInteger(value) || (isString(value) && value); }, message: '"expiresIn" should be a number of seconds or string representing a timespan' },
notBefore: { isValid: function(value) { return isInteger(value) || (isString(value) && value); }, message: '"notBefore" should be a number of seconds or string representing a timespan' },
audience: { isValid: function(value) { return isString(value) || Array.isArray(value); }, message: '"audience" must be a string or array' },
algorithm: { isValid: includes.bind(null, ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'none']), message: '"algorithm" must be a valid string enum value' },
algorithm: { isValid: includes.bind(null, SUPPORTED_ALGS), message: '"algorithm" must be a valid string enum value' },
header: { isValid: isPlainObject, message: '"header" must be an object' },
encoding: { isValid: isString, message: '"encoding" must be a string' },
issuer: { isValid: isString, message: '"issuer" must be a string' },
Expand Down
11 changes: 11 additions & 0 deletions test/async_sign.tests.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var jwt = require('../index');
var expect = require('chai').expect;
var jws = require('jws');
var PS_SUPPORTED = require('../lib/psSupported');

describe('signing a token asynchronously', function() {

Expand Down Expand Up @@ -58,6 +59,16 @@ describe('signing a token asynchronously', function() {
});
});

if (PS_SUPPORTED) {
it('should return error when secret is not a cert for PS256', function(done) {
//this throw an error because the secret is not a cert and PS256 requires a cert.
jwt.sign({ foo: 'bar' }, secret, { algorithm: 'PS256' }, function (err) {
expect(err).to.be.ok;
done();
});
});
}

it('should return error on wrong arguments', function(done) {
//this throw an error because the secret is not a cert and RS256 requires a cert.
jwt.sign({ foo: 'bar' }, secret, { notBefore: {} }, function (err) {
Expand Down
10 changes: 10 additions & 0 deletions test/jwt.asymmetric_signing.tests.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var jwt = require('../index');
var PS_SUPPORTED = require('../lib/psSupported');
var fs = require('fs');
var path = require('path');

Expand All @@ -25,6 +26,15 @@ var algorithms = {
}
};

if (PS_SUPPORTED) {
algorithms.PS256 = {
pub_key: loadKey('pub.pem'),
priv_key: loadKey('priv.pem'),
invalid_pub_key: loadKey('invalid_pub.pem')
};
}


describe('Asymmetric Algorithms', function(){

Object.keys(algorithms).forEach(function (algorithm) {
Expand Down
15 changes: 14 additions & 1 deletion test/rsa-public-key.tests.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
var jwt = require('../');
var PS_SUPPORTED = require('../lib/psSupported');

describe('public key start with BEGIN RSA PUBLIC KEY', function () {

it('should work', function (done) {
it('should work for RS family of algorithms', function (done) {
var fs = require('fs');
var cert_pub = fs.readFileSync(__dirname + '/rsa-public-key.pem');
var cert_priv = fs.readFileSync(__dirname + '/rsa-private.pem');
Expand All @@ -12,4 +13,16 @@ describe('public key start with BEGIN RSA PUBLIC KEY', function () {
jwt.verify(token, cert_pub, done);
});

if (PS_SUPPORTED) {
it('should work for PS family of algorithms', function (done) {
var fs = require('fs');
var cert_pub = fs.readFileSync(__dirname + '/rsa-public-key.pem');
var cert_priv = fs.readFileSync(__dirname + '/rsa-private.pem');

var token = jwt.sign({ foo: 'bar' }, cert_priv, { algorithm: 'PS256'});

jwt.verify(token, cert_pub, done);
});
}

});
6 changes: 6 additions & 0 deletions test/schema.tests.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var jwt = require('../index');
var expect = require('chai').expect;
var fs = require('fs');
var PS_SUPPORTED = require('../lib/psSupported');

describe('schema', function() {

Expand All @@ -21,6 +22,11 @@ describe('schema', function() {
sign({algorithm: 'RS256'});
sign({algorithm: 'RS384'});
sign({algorithm: 'RS512'});
if (PS_SUPPORTED) {
sign({algorithm: 'PS256'});
sign({algorithm: 'PS384'});
sign({algorithm: 'PS512'});
}
sign({algorithm: 'ES256'});
sign({algorithm: 'ES384'});
sign({algorithm: 'ES512'});
Expand Down
11 changes: 11 additions & 0 deletions test/wrong_alg.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var fs = require('fs');
var path = require('path');
var jwt = require('../index');
var JsonWebTokenError = require('../lib/JsonWebTokenError');
var PS_SUPPORTED = require('../lib/psSupported');
var expect = require('chai').expect;


Expand Down Expand Up @@ -29,6 +30,16 @@ describe('when setting a wrong `header.alg`', function () {
});
});

if (PS_SUPPORTED) {
describe('signing with pub key as HS256 and whitelisting only PS256', function () {
it('should not verify', function () {
expect(function () {
jwt.verify(TOKEN, pub, {algorithms: ['PS256']});
}).to.throw(JsonWebTokenError, /invalid algorithm/);
});
});
}

describe('signing with HS256 and checking with HS384', function () {
it('should not verify', function () {
expect(function () {
Expand Down
17 changes: 12 additions & 5 deletions verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,18 @@ var NotBeforeError = require('./lib/NotBeforeError');
var TokenExpiredError = require('./lib/TokenExpiredError');
var decode = require('./decode');
var timespan = require('./lib/timespan');
var PS_SUPPORTED = require('./lib/psSupported');
var jws = require('jws');

var PUB_KEY_ALGS = ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512'];
var RSA_KEY_ALGS = ['RS256', 'RS384', 'RS512'];
var HS_ALGS = ['HS256', 'HS384', 'HS512'];

if (PS_SUPPORTED) {
PUB_KEY_ALGS.splice(3, 0, 'PS256', 'PS384', 'PS512');
RSA_KEY_ALGS.splice(3, 0, 'PS256', 'PS384', 'PS512');
}

module.exports = function (jwtString, secretOrPublicKey, options, callback) {
if ((typeof options === 'function') && !callback) {
callback = options;
Expand Down Expand Up @@ -102,11 +112,8 @@ module.exports = function (jwtString, secretOrPublicKey, options, callback) {

if (!options.algorithms) {
options.algorithms = ~secretOrPublicKey.toString().indexOf('BEGIN CERTIFICATE') ||
~secretOrPublicKey.toString().indexOf('BEGIN PUBLIC KEY') ?
['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512'] :
~secretOrPublicKey.toString().indexOf('BEGIN RSA PUBLIC KEY') ?
['RS256', 'RS384', 'RS512'] :
['HS256', 'HS384', 'HS512'];
~secretOrPublicKey.toString().indexOf('BEGIN PUBLIC KEY') ? PUB_KEY_ALGS :
~secretOrPublicKey.toString().indexOf('BEGIN RSA PUBLIC KEY') ? RSA_KEY_ALGS : HS_ALGS;

}

Expand Down

0 comments on commit eefb9d9

Please sign in to comment.