Skip to content

Commit

Permalink
v3.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
brianloveswords committed Apr 8, 2015
1 parent 6cefc7a commit 585d0e1
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 45 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# Change Log
All notable changes to this project will be documented in this file.

## [3.0.0]
### Changed
- **BREAKING**: `jwt.verify` now requires an `algorithm` parameter, and
`jws.createVerify` requires an `algorithm` option. The `"alg"` field
signature headers is ignored. This mitigates a critical security flaw
in the library which would allow an attacker to generate signatures with
arbitrary contents that would be accepted by `jwt.verify`. See
https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
for details.

## [2.0.0] - 2015-01-30
### Changed
- **BREAKING**: Default payload encoding changed from `binary` to
Expand Down
31 changes: 9 additions & 22 deletions lib/verify-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,6 @@ function securedInputFromJWS(jwsSig) {
return jwsSig.split('.', 2).join('.');
}

function algoFromJWS(jwsSig) {
var err;
const header = headerFromJWS(jwsSig);
if (typeof header != 'object') {
err = new Error("Invalid token: no header in signature '" + jwsSig + "'");
err.code = "MISSING_HEADER";
err.signature = jwsSig;
throw err;
}
if (!header.alg) {
err = new Error("Missing `alg` field in header for signature '"+ jwsSig +"'");
err.code = "MISSING_ALGORITHM";
err.header = header;
err.signature = jwsSig;
throw err;
}
return header.alg;
}

function signatureFromJWS(jwsSig) {
return jwsSig.split('.')[2];
}
Expand All @@ -60,11 +41,16 @@ function isValidJws(string) {
return JWS_REGEX.test(string) && !!headerFromJWS(string);
}

function jwsVerify(jwsSig, secretOrKey) {
function jwsVerify(jwsSig, algorithm, secretOrKey) {
if (!algorithm) {
var err = new Error("Missing algorithm parameter for jws.verify");
err.code = "MISSING_ALGORITHM";
throw err;
}
jwsSig = toString(jwsSig);
const signature = signatureFromJWS(jwsSig);
const securedInput = securedInputFromJWS(jwsSig);
const algo = jwa(algoFromJWS(jwsSig));
const algo = jwa(algorithm);
return algo.verify(securedInput, signature, secretOrKey);
}

Expand Down Expand Up @@ -96,6 +82,7 @@ function VerifyStream(opts) {
const secretOrKey = opts.secret||opts.publicKey||opts.key;
const secretStream = new DataStream(secretOrKey);
this.readable = true;
this.algorithm = opts.algorithm;
this.encoding = opts.encoding;
this.secret = this.publicKey = this.key = secretStream;
this.signature = new DataStream(opts.signature);
Expand All @@ -111,7 +98,7 @@ function VerifyStream(opts) {
}
util.inherits(VerifyStream, Stream);
VerifyStream.prototype.verify = function verify() {
const valid = jwsVerify(this.signature.buffer, this.key.buffer);
const valid = jwsVerify(this.signature.buffer, this.algorithm, this.key.buffer);
const obj = jwsDecode(this.signature.buffer, this.encoding);
this.emit('done', valid, obj);
this.emit('data', valid);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jws",
"version": "2.0.0",
"version": "3.0.0",
"description": "Implementation of JSON Web Signatures",
"main": "index.js",
"directories": {
Expand Down
8 changes: 6 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,18 @@ const signature = jws.sign({
});
```

## jws.verify(signature, secretOrKey)
## jws.verify(signature, algorithm, secretOrKey)

(Synchronous) Returns`true` or `false` for whether a signature matches a
secret or key.

`signature` is a JWS Signature. `secretOrKey` is a string or
`signature` is a JWS Signature. `header.alg` must be a value found in `jws.ALGORITHMS`.
See above for a table of supported algorithms. `secretOrKey` is a string or
buffer containing either the secret for HMAC algorithms, or the PEM
encoded public key for RSA and ECDSA.

Note that the `"alg"` value from the signature header is ignored.


## jws.decode(signature)

Expand Down Expand Up @@ -127,6 +130,7 @@ Returns a new VerifyStream object.
Options:

* `signature`
* `algorithm`
* `key` || `publicKey` || `secret`
* `encoding` (Optional, defaults to 'utf8')

Expand Down
53 changes: 33 additions & 20 deletions test/jws.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ const payload = {

BITS.forEach(function (bits) {
test('HMAC using SHA-'+bits+' hash algorithm', function (t) {
const header = { alg: 'HS'+bits, typ: 'JWT' };
const alg = 'HS'+bits;
const header = { alg: alg, typ: 'JWT' };
const secret = 'sup';
const jwsObj = jws.sign({
header: header,
Expand All @@ -55,8 +56,9 @@ BITS.forEach(function (bits) {
encoding: 'utf8',
});
const parts = jws.decode(jwsObj);
t.ok(jws.verify(jwsObj, secret), 'should verify');
t.notOk(jws.verify(jwsObj, 'something else'), 'should not verify');
t.ok(jws.verify(jwsObj, alg, secret), 'should verify');
t.notOk(jws.verify(jwsObj, alg, 'something else'), 'should not verify with non-matching secret');
t.notOk(jws.verify(jwsObj, 'RS'+bits, secret), 'should not verify with non-matching algorithm');
t.same(parts.payload, payload, 'should match payload');
t.same(parts.header, header, 'should match header');
t.end();
Expand All @@ -65,7 +67,8 @@ BITS.forEach(function (bits) {

BITS.forEach(function (bits) {
test('RSASSA using SHA-'+bits+' hash algorithm', function (t) {
const header = { alg: 'RS'+bits };
const alg = 'RS'+bits;
const header = { alg: alg };
const privateKey = rsaPrivateKey;
const publicKey = rsaPublicKey;
const wrongPublicKey = rsaWrongPublicKey;
Expand All @@ -75,8 +78,9 @@ BITS.forEach(function (bits) {
privateKey: privateKey
});
const parts = jws.decode(jwsObj, { json: true });
t.ok(jws.verify(jwsObj, publicKey), 'should verify');
t.notOk(jws.verify(jwsObj, wrongPublicKey), 'should not verify');
t.ok(jws.verify(jwsObj, alg, publicKey), 'should verify');
t.notOk(jws.verify(jwsObj, alg, wrongPublicKey), 'should not verify with non-matching public key');
t.notOk(jws.verify(jwsObj, 'HS'+bits, publicKey), 'should not verify with non-matching algorithm');
t.same(parts.payload, payload, 'should match payload');
t.same(parts.header, header, 'should match header');
t.end();
Expand All @@ -86,7 +90,8 @@ BITS.forEach(function (bits) {
BITS.forEach(function (bits) {
const curve = CURVES[bits];
test('ECDSA using P-'+curve+' curve and SHA-'+bits+' hash algorithm', function (t) {
const header = { alg: 'ES'+bits };
const alg = 'ES'+bits;
const header = { alg: alg };
const privateKey = ecdsaPrivateKey['256'];
const publicKey = ecdsaPublicKey['256'];
const wrongPublicKey = ecdsaWrongPublicKey['256'];
Expand All @@ -96,24 +101,27 @@ BITS.forEach(function (bits) {
privateKey: privateKey
});
const parts = jws.decode(jwsObj);
t.ok(jws.verify(jwsObj, publicKey), 'should verify');
t.notOk(jws.verify(jwsObj, wrongPublicKey), 'should not verify');
t.ok(jws.verify(jwsObj, alg, publicKey), 'should verify');
t.notOk(jws.verify(jwsObj, alg, wrongPublicKey), 'should not verify with non-matching public key');
t.notOk(jws.verify(jwsObj, 'HS'+bits, publicKey), 'should not verify with non-matching algorithm');
t.same(parts.payload, payloadString, 'should match payload');
t.same(parts.header, header, 'should match header');
t.end();
});
});

test('No digital signature or MAC value included', function (t) {
const header = { alg: 'none' };
const alg = 'none';
const header = { alg: alg };
const payload = 'oh hey José!';
const jwsObj = jws.sign({
header: header,
payload: payload,
});
const parts = jws.decode(jwsObj);
t.ok(jws.verify(jwsObj), 'should verify');
t.ok(jws.verify(jwsObj, 'anything'), 'should still verify');
t.ok(jws.verify(jwsObj, alg), 'should verify');
t.ok(jws.verify(jwsObj, alg, 'anything'), 'should still verify');
t.notOk(jws.verify(jwsObj, 'HS256', 'anything'), 'should not verify with non-matching algorithm');
t.same(parts.payload, payload, 'should match payload');
t.same(parts.header, header, 'should match header');
t.end();
Expand All @@ -128,7 +136,7 @@ test('Streaming sign: HMAC', function (t) {
});
dataStream.pipe(sig.payload);
sig.on('done', function (signature) {
t.ok(jws.verify(signature, secret), 'should verify');
t.ok(jws.verify(signature, 'HS256', secret), 'should verify');
t.end();
});
});
Expand All @@ -148,8 +156,8 @@ test('Streaming sign: RSA', function (t) {
});

sig.on('done', function (signature) {
t.ok(jws.verify(signature, publicKey), 'should verify');
t.notOk(jws.verify(signature, wrongPublicKey), 'should not verify');
t.ok(jws.verify(signature, 'RS256', publicKey), 'should verify');
t.notOk(jws.verify(signature, 'RS256', wrongPublicKey), 'should not verify');
t.same(jws.decode(signature).payload, readfile('data.txt'), 'got all the data');
t.end();
});
Expand All @@ -166,8 +174,8 @@ test('Streaming sign: RSA, predefined streams', function (t) {
privateKey: privateKeyStream
});
sig.on('done', function (signature) {
t.ok(jws.verify(signature, publicKey), 'should verify');
t.notOk(jws.verify(signature, wrongPublicKey), 'should not verify');
t.ok(jws.verify(signature, 'RS256', publicKey), 'should verify');
t.notOk(jws.verify(signature, 'RS256', wrongPublicKey), 'should not verify');
t.same(jws.decode(signature).payload, readfile('data.txt'), 'got all the data');
t.end();
});
Expand All @@ -182,7 +190,7 @@ test('Streaming verify: ECDSA', function (t) {
payload: dataStream,
privateKey: privateKeyStream
});
const verifier = jws.createVerify();
const verifier = jws.createVerify({algorithm: 'ES512'});
sigStream.pipe(verifier.signature);
publicKeyStream.pipe(verifier.key);
verifier.on('done', function (valid) {
Expand All @@ -201,6 +209,7 @@ test('Streaming verify: ECDSA, with invalid key', function (t) {
privateKey: privateKeyStream
});
const verifier = jws.createVerify({
algorithm: 'ES512',
signature: sigStream,
publicKey: publicKeyStream,
});
Expand All @@ -225,14 +234,18 @@ test('jws.decode: with a bogus header ', function (t) {
t.end();
});

test('jws.decode: missing algo in header', function (t) {
test('jws.verify: missing or invalid algorithm', function (t) {
const header = Buffer('{"something":"not an algo"}').toString('base64');
const payload = Buffer('sup').toString('base64');
const sig = header + '.' + payload + '.';
try { jws.verify(sig, 'whatever') }
try { jws.verify(sig) }
catch (e) {
t.same(e.code, 'MISSING_ALGORITHM');
}
try { jws.verify(sig, 'whatever') }
catch (e) {
t.ok(e.message.match('"whatever" is not a valid algorithm.'));
}
t.end();
});

Expand Down

0 comments on commit 585d0e1

Please sign in to comment.