1,287 changes: 657 additions & 630 deletions coverage/lcov-report/node-jsjws/lib/jsjws.js.html

Large diffs are not rendered by default.

2,353 changes: 1,184 additions & 1,169 deletions coverage/lcov.info

Large diffs are not rendered by default.

85 changes: 47 additions & 38 deletions lib/jsjws.js
Original file line number Diff line number Diff line change
Expand Up @@ -11731,32 +11731,69 @@ KJUR.jws.JWS = function() {
* @throws if sJWS is not comma separated string such like "Header.Payload.Signature".
* @throws if JWS Header is a malformed JSON string.
*/
this.verifyJWSByKey = function(sJWS, key) {
this.parseJWS(sJWS, !key.verifyString);
this.verifyJWSByKey = function(sJWS, key, allowed_algs) {
this.parseJWS(sJWS, (!key) || !key.verifyString);
var headP = this.parsedJWS.headP;
var alg = headP.alg;
if (alg === 'none') {
if (alg === undefined)
{
throw new Error('alg not present');
}
allowed_algs = allowed_algs || [];
function is_allowed(a)
{
if (Array.isArray(allowed_algs))
{
return allowed_algs.indexOf(a) >= 0;
}
else
{
return allowed_algs[a] !== undefined;
}
}
if (!is_allowed(alg))
{
throw new Error('algorithm not allowed: ' + alg);
}

if (alg === 'none')
{
return true;
}
var hashAlg = _jws_getHashAlgFromParsedHead(headP);
if (!key)
{
if (!is_allowed('none'))
{
throw new Error('no key but none alg not allowed');
}
return true;
}

var hashAlg = _jws_getHashAlgFromParsedHead(headP);
alg = alg.substr(0, 2);
var isPSS = alg === "PS";
var r;

if (key.hashAndVerify) {
return key.hashAndVerify(hashAlg,
r = key.hashAndVerify(hashAlg,
new Buffer(this.parsedJWS.si, 'utf8'),
new Buffer(b64utob64(this.parsedJWS.sigvalB64U), 'base64'),
null,
isPSS);
} else if (isPSS) {
return key.verifyStringPSS(this.parsedJWS.si,
r = key.verifyStringPSS(this.parsedJWS.si,
this.parsedJWS.sigvalH, hashAlg);
} else if (alg === "HS") {
return const_time_equal(hmac(hashAlg, key, this.parsedJWS.si), b64utob64(this.parsedJWS.sigvalB64U));
r = const_time_equal(hmac(hashAlg, key, this.parsedJWS.si), b64utob64(this.parsedJWS.sigvalB64U));
} else {
return key.verifyString(this.parsedJWS.si,
r = key.verifyString(this.parsedJWS.si,
this.parsedJWS.sigvalH);
}
if (!r)
{
throw new Error('failed to verify');
}
return r;
};

/**
Expand Down Expand Up @@ -12147,23 +12184,14 @@ KJUR.jws.JWT.prototype.verifyJWTByKey = function (jwt, options, key, allowed_alg
options = null;
}

if (key)
{
this.verifyJWSByKey(jwt, key);
}
else
{
this.processJWS(jwt);
}
this.verifyJWSByKey(jwt, key, allowed_algs);

options = options || {};
allowed_algs = allowed_algs || [];

var header = this.getParsedHeader(),
claims = this.getParsedPayload(),
now = Math.floor(new Date().getTime() / 1000),
iat_skew = options.iat_skew || 0,
is_allowed;
iat_skew = options.iat_skew || 0;

if (!header)
{
Expand All @@ -12175,25 +12203,6 @@ KJUR.jws.JWT.prototype.verifyJWTByKey = function (jwt, options, key, allowed_alg
throw new Error('no claims');
}

if (header.alg === undefined)
{
throw new Error('alg not present');
}

if (allowed_algs.indexOf !== undefined)
{
is_allowed = allowed_algs.indexOf(header.alg) >= 0;
}
else
{
is_allowed = allowed_algs[header.alg] !== undefined;
}

if (!is_allowed)
{
throw new Error('algorithm not allowed: ' + header.alg);
}

if (header.typ === undefined)
{
if (!options.checks_optional)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jsjws",
"description": "Wraps jsjws (http://kjur.github.io/jsjws/) so it works on Node.js and uses ursa for performance",
"version": "1.0.1",
"version": "2.0.0",
"homepage": "https://github.com/davedoesdev/node-jsjws",
"author": {
"name": "David Halls",
Expand Down
71 changes: 58 additions & 13 deletions patches/jsjws.patch
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/jws-2.0.js b/jws-2.0.js
index cc0561e..0eb6246 100755
index cc0561e..49b55bb 100755
--- a/jws-2.0.js
+++ b/jws-2.0.js
@@ -23,6 +23,45 @@
Expand Down Expand Up @@ -66,43 +66,88 @@ index cc0561e..0eb6246 100755
throw "JWS signature is not a form of 'Head.Payload.SigValue'.";
}
var b6Head = RegExp.$1;
@@ -203,20 +242,28 @@ KJUR.jws.JWS = function() {
@@ -202,24 +241,69 @@ KJUR.jws.JWS = function() {
* @throws if sJWS is not comma separated string such like "Header.Payload.Signature".
* @throws if JWS Header is a malformed JSON string.
*/
this.verifyJWSByKey = function(sJWS, key) {
- this.verifyJWSByKey = function(sJWS, key) {
- this.parseJWS(sJWS);
- var hashAlg = _jws_getHashAlgFromParsedHead(this.parsedJWS.headP);
- var isPSS = this.parsedJWS.headP['alg'].substr(0, 2) == "PS";
+ this.parseJWS(sJWS, !key.verifyString);
+ this.verifyJWSByKey = function(sJWS, key, allowed_algs) {
+ this.parseJWS(sJWS, (!key) || !key.verifyString);
+ var headP = this.parsedJWS.headP;
+ var alg = headP.alg;
+ if (alg === 'none') {
+ if (alg === undefined)
+ {
+ throw new Error('alg not present');
+ }
+ allowed_algs = allowed_algs || [];
+ function is_allowed(a)
+ {
+ if (Array.isArray(allowed_algs))
+ {
+ return allowed_algs.indexOf(a) >= 0;
+ }
+ else
+ {
+ return allowed_algs[a] !== undefined;
+ }
+ }
+ if (!is_allowed(alg))
+ {
+ throw new Error('algorithm not allowed: ' + alg);
+ }
+
+ if (alg === 'none')
+ {
+ return true;
+ }
+ var hashAlg = _jws_getHashAlgFromParsedHead(headP);
+ if (!key)
+ {
+ if (!is_allowed('none'))
+ {
+ throw new Error('no key but none alg not allowed');
+ }
+ return true;
+ }
+
+ var hashAlg = _jws_getHashAlgFromParsedHead(headP);
+ alg = alg.substr(0, 2);
+ var isPSS = alg === "PS";
+ var r;

if (key.hashAndVerify) {
return key.hashAndVerify(hashAlg,
- return key.hashAndVerify(hashAlg,
- new Buffer(this.parsedJWS.si, 'utf8').toString('base64'),
- b64utob64(this.parsedJWS.sigvalB64U),
- 'base64',
+ r = key.hashAndVerify(hashAlg,
+ new Buffer(this.parsedJWS.si, 'utf8'),
+ new Buffer(b64utob64(this.parsedJWS.sigvalB64U), 'base64'),
+ null,
isPSS);
} else if (isPSS) {
return key.verifyStringPSS(this.parsedJWS.si,
- return key.verifyStringPSS(this.parsedJWS.si,
+ r = key.verifyStringPSS(this.parsedJWS.si,
this.parsedJWS.sigvalH, hashAlg);
- } else {
- return key.verifyString(this.parsedJWS.si,
+ } else if (alg === "HS") {
+ return const_time_equal(hmac(hashAlg, key, this.parsedJWS.si), b64utob64(this.parsedJWS.sigvalB64U));
+ r = const_time_equal(hmac(hashAlg, key, this.parsedJWS.si), b64utob64(this.parsedJWS.sigvalB64U));
+ } else {
return key.verifyString(this.parsedJWS.si,
+ r = key.verifyString(this.parsedJWS.si,
this.parsedJWS.sigvalH);
}
@@ -243,12 +290,17 @@ KJUR.jws.JWS = function() {
+ if (!r)
+ {
+ throw new Error('failed to verify');
+ }
+ return r;
};

/**
@@ -243,12 +327,17 @@ KJUR.jws.JWS = function() {
};

// ==== JWS Generation =========================================================
Expand All @@ -123,7 +168,7 @@ index cc0561e..0eb6246 100755
throw "JWS signature algorithm not supported: " + sigAlg;
if (sigAlg.substr(2) == "256") hashAlg = "sha256";
if (sigAlg.substr(2) == "512") hashAlg = "sha512";
@@ -268,21 +320,26 @@ KJUR.jws.JWS = function() {
@@ -268,21 +357,26 @@ KJUR.jws.JWS = function() {
return sigValue;
};

Expand Down Expand Up @@ -160,7 +205,7 @@ index cc0561e..0eb6246 100755
return hextob64u(key.signString(sSI, hashAlg));
}
};
@@ -339,7 +396,7 @@ KJUR.jws.JWS = function() {
@@ -339,7 +433,7 @@ KJUR.jws.JWS = function() {
if (!this.isSafeJSONString(sHead, obj, 'headP'))
throw "JWS Head is not safe JSON string: " + sHead;
var sSI = _getSignatureInputByString(sHead, sPayload);
Expand Down
2 changes: 1 addition & 1 deletion python-jws
24 changes: 23 additions & 1 deletion test/alg_none_verification_spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/*global expect: false,
it: false,
describe: false,
jsjws: false */
jsjws: false,
generated_key: false,
payload: false */
/*jslint node: true */
"use strict";

Expand Down Expand Up @@ -29,5 +31,25 @@ describe('alg-none-verification', function ()
jwt.verifyJWTByKey(jwt_alg_none, 'anysecrethere');
}).to.throw('algorithm not allowed: none');
});

it('should fail to verify the token when public key not specified and no allowed algorithm specified', function ()
{
var jwt = new jsjws.JWT();
expect(function ()
{
jwt.verifyJWTByKey(jwt_alg_none);
}).to.throw('algorithm not allowed: none');
});

it('should fail to verify token when public key not specified and none alg is not allowed', function ()
{
var expires = new Date(), token;
expires.setSeconds(expires.getSeconds() + 10);
token = new jsjws.JWT().generateJWTByKey({alg: 'RS256'}, payload, expires, generated_key);
expect(function ()
{
new jsjws.JWT().verifyJWTByKey(token, null, ['RS256']);
}).to.throw('no key but none alg not allowed');
});
});

14 changes: 7 additions & 7 deletions test/browser_interop_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function verify_premade_browser_sig(alg, pub_key)
', pub_key=' + pub_key, function ()
{
var jws = new jsjws.JWS();
expect(jws.verifyJWSByKey(browser_sigs[alg], pub_keys[alg][pub_key])).to.equal(true);
expect(jws.verifyJWSByKey(browser_sigs[alg], pub_keys[alg][pub_key], [alg])).to.equal(true);
expect(jws.getParsedPayload()).to.eql(payload);
expect(jws.getParsedHeader()).to.eql({ alg: alg });
});
Expand Down Expand Up @@ -86,7 +86,7 @@ function verify_browser_sig(alg, pub_key)
try
{
var jws = new jsjws.JWS();
expect(jws.verifyJWSByKey(r.sjws, pub_keys[alg][pub_key])).to.equal(true);
expect(jws.verifyJWSByKey(r.sjws, pub_keys[alg][pub_key], [alg])).to.equal(true);
expect(jws.getUnparsedPayload()).to.equal(spayload);
expect(jws.getUnparsedHeader()).to.equal(header);
}
Expand All @@ -110,7 +110,7 @@ function verify_sig_in_browser(alg, priv_key)
{
var sjws = new jsjws.JWS().generateJWSByKey(header, spayload, priv_keys[alg][priv_key]),

f = function (pub_pem, sjws)
f = function (pub_pem, sjws, alg)
{
var r = {}, key, jws;

Expand All @@ -127,7 +127,7 @@ function verify_sig_in_browser(alg, priv_key)
}

jws = new KJUR.jws.JWS();
r.verified = jws.verifyJWSByKey(sjws, key);
r.verified = jws.verifyJWSByKey(sjws, key, [alg]);

if (r.verified)
{
Expand All @@ -144,7 +144,7 @@ function verify_sig_in_browser(alg, priv_key)
};

browser.execute('return ' + f + '.apply(this, arguments)',
[pub_keys[alg].default || pub_pem, sjws],
[pub_keys[alg].default || pub_pem, sjws, alg],
function (err, r)
{
if (err)
Expand Down Expand Up @@ -228,14 +228,14 @@ function generate_key_in_browser_and_verify_sig(alg)

pub_key = jsjws.createPublicKey(r.pub_pem, 'utf8');
jws = new jsjws.JWS();
expect(jws.verifyJWSByKey(r.sjws, pub_key)).to.equal(true);
expect(jws.verifyJWSByKey(r.sjws, pub_key, [alg])).to.equal(true);
expect(jws.getUnparsedPayload()).to.equal(spayload);
expect(jws.getUnparsedHeader()).to.equal(header);

pub_key = new jsjws.SlowRSAKey();
pub_key.readPublicKeyFromPEMString(r.pub_pem);
jws = new jsjws.JWS();
expect(jws.verifyJWSByKey(r.sjws, pub_key)).to.equal(true);
expect(jws.verifyJWSByKey(r.sjws, pub_key, [alg])).to.equal(true);
expect(jws.getUnparsedPayload()).to.equal(spayload);
expect(jws.getUnparsedHeader()).to.equal(header);
}
Expand Down
2 changes: 1 addition & 1 deletion test/cert_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('X509 certificates', function ()
pub_key = jsjws.createPublicKey(slow_pub_key.publicKeyToPEMString()),
jws = new jsjws.JWS();

expect(jws.verifyJWSByKey(sig, pub_key));
expect(jws.verifyJWSByKey(sig, pub_key, ['PS256']));
expect(jws.getParsedHeader()).to.eql(header);
expect(jws.getParsedPayload()).to.eql(payload);

Expand Down
6 changes: 4 additions & 2 deletions test/generate_key_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ function check_generate_key(alg, type, gen)

pub_key = jsjws.createPublicKey(pub_pem, 'utf8');
jws = new jsjws.JWS();
expect(jws.verifyJWSByKey(sjws, pub_key)).to.equal(true);
expect(jws.verifyJWSByKey(sjws, pub_key, [alg])).to.equal(true);
expect(jws.getUnparsedPayload()).to.equal(spayload);
expect(jws.getUnparsedHeader()).to.equal(header);

pub_key = new jsjws.SlowRSAKey();
pub_key.readPublicKeyFromPEMString(pub_pem);
jws = new jsjws.JWS();
expect(jws.verifyJWSByKey(sjws, pub_key)).to.equal(true);
expect(jws.verifyJWSByKey(sjws, pub_key, [alg])).to.equal(true);
expect(jws.getUnparsedPayload()).to.equal(spayload);
expect(jws.getUnparsedHeader()).to.equal(header);

Expand All @@ -47,6 +47,8 @@ function check_generate_key(alg, type, gen)

describe('generate_key', function ()
{
this.timeout(15 * 60 * 1000);

var algs = ['RS256', 'RS512', 'PS256', 'PS512'], i,

fast = function (header, cb)
Expand Down
4 changes: 2 additions & 2 deletions test/generate_verify_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function generate_verify(alg, priv_name, pub_name)
var jws = new jsjws.JWS();
expect(function ()
{
jws.verifyJWSByKey(sjws, global.generated_key);
jws.verifyJWSByKey(sjws, global.generated_key, [alg]);
}).to.throw(Error);

if (typeof pub_key === 'function')
Expand All @@ -96,7 +96,7 @@ function generate_verify(alg, priv_name, pub_name)
else
{
jws = new jsjws.JWS();
expect(jws.verifyJWSByKey(sjws, pub_key)).to.equal(true);
expect(jws.verifyJWSByKey(sjws, pub_key, [alg])).to.equal(true);
expect(jws.getParsedPayload()).to.eql(payload);
expect(jws.getParsedHeader()).to.eql(header);
cb();
Expand Down
4 changes: 2 additions & 2 deletions test/google_jwt_oauth_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('google-jwt-oauth', function ()

expect(function ()
{
jwt.verifyJWTByKey(google_jwt_example, null, ['RS256']);
jwt.verifyJWTByKey(google_jwt_example, null, ['RS256', 'none']);
}).to.throw('no not before claim');
});

Expand All @@ -46,7 +46,7 @@ describe('google-jwt-oauth', function ()
{
iat_skew: adjust,
checks_optional: true
}, null, ['RS256'])).to.equal(true);
}, null, ['RS256', 'none'])).to.equal(true);

expect(jwt.getParsedHeader()).to.eql(
{
Expand Down
32 changes: 2 additions & 30 deletions wrap/adapt.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,23 +210,14 @@ KJUR.jws.JWT.prototype.verifyJWTByKey = function (jwt, options, key, allowed_alg
options = null;
}

if (key)
{
this.verifyJWSByKey(jwt, key);
}
else
{
this.processJWS(jwt);
}
this.verifyJWSByKey(jwt, key, allowed_algs);

options = options || {};
allowed_algs = allowed_algs || [];

var header = this.getParsedHeader(),
claims = this.getParsedPayload(),
now = Math.floor(new Date().getTime() / 1000),
iat_skew = options.iat_skew || 0,
is_allowed;
iat_skew = options.iat_skew || 0;

if (!header)
{
Expand All @@ -238,25 +229,6 @@ KJUR.jws.JWT.prototype.verifyJWTByKey = function (jwt, options, key, allowed_alg
throw new Error('no claims');
}

if (header.alg === undefined)
{
throw new Error('alg not present');
}

if (allowed_algs.indexOf !== undefined)
{
is_allowed = allowed_algs.indexOf(header.alg) >= 0;
}
else
{
is_allowed = allowed_algs[header.alg] !== undefined;
}

if (!is_allowed)
{
throw new Error('algorithm not allowed: ' + header.alg);
}

if (header.typ === undefined)
{
if (!options.checks_optional)
Expand Down
19 changes: 13 additions & 6 deletions wrap/docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
Node.js wrapper around [jsjws](https://github.com/kjur/jsjws) (a [JSON Web Signature](http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-14) library).
- **Note:** Versions 2.0.0 and later fix [a vulnerability](https://www.timmclean.net/2015/02/25/jwt-alg-none.html) in JSON Web Signature and JSON Web Token verification so please upgrade if you're using this functionality. The API has changed so you will need to update your application. [verifyJWSByKey](#jwsprototypeverifyjwsbykeyjws-key-allowed_algs) and [verifyJWTByKey](#jwtprototypeverifyjwtbykeyjwt-options-key-allowed_algs) now require you to specify which signature algorithms are allowed.
- Uses [ursa](https://github.com/Obvious/ursa) for performance.
- Supports [__RS256__, __RS512__](http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-14#section-3.3), [__PS256__, __PS512__](http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-14#section-3.5), [__HS256__, __HS512__](http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-14#section-3.2) and [__none__](http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-14#section-3.6) signature algorithms.
- Basic [JSON Web Token](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) functionality. **Note:** Versions 0.7.2 and later fix [a vulnerability](https://www.timmclean.net/2015/02/25/jwt-alg-none.html) in JSON Web Token verification so please upgrade if you're using this functionality. [verifyJWTByKey](#jwtprototypeverifyjwtbykeyjwt-options-key-allowed_algs) no longer accepts unsigned tokens when you supply a key and requires specifying which signature algorithms are allowed.
- Basic [JSON Web Token](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) functionality.
- Unit tests, including tests for interoperability with [node-jws](https://github.com/brianloveswords/node-jws), [python-jws](https://github.com/brianloveswords/python-jws) and jsjws in the browser (using [PhantomJS](http://phantomjs.org/)).
Example:
Expand All @@ -17,7 +18,7 @@ var header = { alg: 'PS256' };
var payload = { foo: 'bar', wup: 90 };
var sig = new jsjws.JWS().generateJWSByKey(header, payload, key);
var jws = new jsjws.JWS();
assert(jws.verifyJWSByKey(sig, key));
assert(jws.verifyJWSByKey(sig, key, ['PS256']));
assert.deepEqual(jws.getParsedHeader(), header);
assert.deepEqual(jws.getParsedPayload(), payload);
```
Expand Down Expand Up @@ -45,7 +46,7 @@ var priv_key = jsjws.createPrivateKey(priv_pem, 'utf8');
var pub_key = jsjws.createPublicKey(pub_pem, 'utf8');
var sig = new jsjws.JWS().generateJWSByKey(header, payload, priv_key);
var jws = new jsjws.JWS();
assert(jws.verifyJWSByKey(sig, pub_key));
assert(jws.verifyJWSByKey(sig, pub_key, ['RS256']));
assert.deepEqual(jws.getParsedHeader(), header);
assert.equal(jws.getUnparsedPayload(), payload);
```
Expand Down Expand Up @@ -184,13 +185,19 @@ Verify a JSON Web Signature.
@param {String} jws The JSON Web Signature to verify.
@param {PublicKey} key The public key to be used to verify the signature. For `HS256` and `HS512`, pass a string or `Buffer`. Note: if you pass `null` then the signature will not be verified.
@param {PublicKey} key The public key to be used to verify the signature. For `HS256` and `HS512`, pass a string or `Buffer`. Note: if you pass `null` and `allowed_algs` contains `none` then the signature will not be verified.
@return {Boolean} `true` if the signature was verified successfully using the public key or the JSON Web Signature's algorithm is `none`.
@param {Array|Object} allowed_algs Algorithms expected to be used to sign the signature. If you pass an `Object` then its properties define the set of algorithms expected.
@return {Boolean} `true` if the signature was verified successfully. The JWS must pass the following tests:
- Its header must contain a property `alg` with a value in `allowed_algs`.
- Its signature must verify using `key` (unless its algorithm is `none` and `none` is in `allowed_algs`).
@throws {Error} If the signature failed to verify.
*/
JWS.prototype.verifyJWSByKey = function (jws, key) { return undefined; };
JWS.prototype.verifyJWSByKey = function (jws, key, allowed_algs) { return undefined; };

/**
Get the header (metadata) from a JSON Web Signature. Call this after verifying the signature (with JWS.prototype.verifyJWSByKey).
Expand Down