Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add PS JWA support #573

Merged
merged 1 commit into from Feb 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .travis.yml
@@ -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
Expand Up @@ -339,9 +339,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
@@ -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
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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
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