Skip to content

Commit

Permalink
v0.1.0: Major rewrite to use node-jwa
Browse files Browse the repository at this point in the history
  • Loading branch information
brianloveswords committed Feb 10, 2013
1 parent d2e3dc6 commit f480ca4
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 429 deletions.
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
node_modules
/test/keys
/test/private.pem
/test/public.pem
/test/wrong-public.pem
/test/*.pem
17 changes: 17 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Copyright (c) 2013 Brian J. Brennan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 changes: 13 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
verbose: test/keys
@node test/*.test.js

test: test/keys
@./node_modules/.bin/tap test/*.test.js

Expand All @@ -8,12 +11,20 @@ test/keys:
@openssl rsa -in test/rsa-wrong-private.pem -pubout > test/rsa-wrong-public.pem
@openssl ecparam -out test/ec256-private.pem -name secp256k1 -genkey
@openssl ecparam -out test/ec256-wrong-private.pem -name secp256k1 -genkey
@openssl ecparam -out test/ec384-private.pem -name secp384r1 -genkey
@openssl ecparam -out test/ec384-wrong-private.pem -name secp384r1 -genkey
@openssl ecparam -out test/ec512-private.pem -name secp521r1 -genkey
@openssl ecparam -out test/ec512-wrong-private.pem -name secp521r1 -genkey
@openssl ec -in test/ec256-private.pem -pubout > test/ec256-public.pem
@openssl ec -in test/ec256-wrong-private.pem -pubout > test/ec256-wrong-public.pem
@openssl ec -in test/ec384-private.pem -pubout > test/ec384-public.pem
@openssl ec -in test/ec384-wrong-private.pem -pubout > test/ec384-wrong-public.pem
@openssl ec -in test/ec512-private.pem -pubout > test/ec512-public.pem
@openssl ec -in test/ec512-wrong-private.pem -pubout > test/ec512-wrong-public.pem
@touch test/keys

clean:
rm test/*.pem
rm test/keys
@rm test/*.pem
@rm test/keys

.PHONY: test
187 changes: 60 additions & 127 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,121 +1,52 @@
const util = require('util');
const base64url = require('base64url');
const crypto = require('crypto');

exports.sign = function jwsSign() {
var opts, header, payload, secretOrKey;
if (arguments.length === 2) {
secretOrKey = arguments[1];
header = {
alg: algorithmFromSecret(secretOrKey)
};
return jwsSign({
header: header,
payload: arguments[0],
secret: secretOrKey,
});
}

if (arguments.length === 1) {
opts = arguments[0];
header = opts.header;
payload = opts.payload;
if (typeof payload === 'object')
payload = JSON.stringify(payload);
const signers = {
HS256: jwsHS256Sign,
RS256: jwsRS256Sign,
EC256: jwsEC256Sign,
none: jwsNoneSign,
};
const signerFn = signers[header.alg];
return signerFn(header, payload, (opts.secret || opts.key))
}
const jwa = require('jwa');

function toString(obj) {
if (typeof obj === 'string')
return obj;
if (typeof obj === 'number' || Buffer.isBuffer(obj))
return obj.toString();
return JSON.stringify(obj);
}

function jwsSecuredInput(header, payload) {
const encodedHeader = base64url(header);
const encodedPayload = base64url(payload);
const encodedHeader = base64url(toString(header));
const encodedPayload = base64url(toString(payload));
return util.format('%s.%s', encodedHeader, encodedPayload);
}

function algorithmFromSecret(secretOrKey) {
secretOrKey = secretOrKey.toString();
const RSA_INDICATOR = '-----BEGIN RSA PRIVATE KEY-----';
const EC_INDICATOR = '-----BEGIN EC PRIVATE KEY-----';
if (secretOrKey.indexOf(RSA_INDICATOR) > -1)
return 'RS256';
if (secretOrKey.indexOf(EC_INDICATOR) > -1)
return 'EC256';
return 'HS256';
}

function jwsNoneSign(header, payload) {
header = JSON.stringify(header);
return jwsOutput(header, payload, '');
}

// The latest version of openssl doesn't yet support `ecdsa-with-sha256`
// as a message digest algorithm, only `ecdsa-with-sha1` (despite the
// fact it supports both separately). Once that is implemented, we can
// implement this.
function jwsEC256Sign(header, payload, key) {
throw "Not implemented, yet";
function jwsSign(opts) {
const header = opts.header;
const payload = opts.payload;
const secretOrKey = opts.secret || opts.privateKey;
const algo = jwa(header.alg);
const securedInput = jwsSecuredInput(header, payload);
const signature = algo.sign(securedInput, secretOrKey);
return util.format('%s.%s', securedInput, signature);
}

function jwsRS256Sign(header, payload, key) {
header = JSON.stringify(header);
const signature = createRS256Signature(header, payload, key);
return jwsOutput(header, payload, signature);
function headerFromJWS(jwsSig) {
const encodedHeader = jwsSig.split('.', 1)[0];
return JSON.parse(base64url.decode(encodedHeader));
}

function createRS256Signature(header, payload, key) {
const signer = crypto.createSign('RSA-SHA256', key);
const securedInput = jwsSecuredInput(header, payload);
const signature = (signer.update(securedInput), signer.sign(key, 'base64'));
return base64url.fromBase64(signature);
function securedInputFromJWS(jwsSig) {
return jwsSig.split('.', 2).join('.');
}

function jwsHS256Sign(header, payload, secret) {
header = JSON.stringify(header);
const signature = createHS256Signature(header, payload, secret);
return jwsOutput(header, payload, signature);
function algoFromJWS(jwsSig) {
return headerFromJWS(jwsSig).alg;
}

function createHS256Signature(header, payload, secret) {
const hmac = crypto.createHmac('SHA256', secret);
const securedInput = jwsSecuredInput(header, payload);
const signature = (hmac.update(securedInput), hmac.digest('base64'));
return base64url.fromBase64(signature);
function signatureFromJWS(jwsSig) {
return jwsSig.split('.')[2];
}

function jwsOutput(header, payload, signature) {
return util.format(
'%s.%s.%s',
base64url(header),
base64url(payload),
signature);
}

exports.verify = function jwsVerify(jwsObject, secretOrKey) {
if (!secretOrKey)
throw TypeError('jws.verify requires two arguments, a JWS object and a secret');
if (!isValidJws(jwsObject))
throw TypeError('jws.verify requires a valid JWS object for the first argument')
const parts = jwsObject.split('.');
const encodedHeader = parts[0];
const encodedPayload = parts[1];
const encodedSignature = parts[2];
const rawHeader = base64url.decode(encodedHeader);
const payload = base64url.decode(encodedPayload);
const header = JSON.parse(rawHeader);
const verifiers = {
HS256: jwsHS256Verify,
RS256: jwsRS256Verify,
none: jwsNoneVerify,
};
const verifierFn = verifiers[header.alg];
return verifierFn(rawHeader, payload, secretOrKey, encodedSignature)
function payloadFromJWS(jwsSig) {
const payload = jwsSig.split('.')[1];
return base64url.decode(payload);
}

function isValidJws(string) {
Expand All @@ -127,38 +58,40 @@ function isValidJws(string) {
return true;
}

function jwsHS256Verify(header, payload, secret, expectedSignature) {
const calculatedSignature =
createHS256Signature(header, payload, secret);
return expectedSignature === calculatedSignature;
function jwsVerify(jwsSig, secretOrKey) {
const signature = signatureFromJWS(jwsSig);
const securedInput = securedInputFromJWS(jwsSig);
const algo = jwa(algoFromJWS(jwsSig));
return algo.verify(securedInput, signature, secretOrKey);
}

function jwsRS256Verify(header, payload, publicKey, signature) {
const verifier = crypto.createVerify('RSA-SHA256');
const securedInput = jwsSecuredInput(header, payload);
signature = base64url.toBase64(signature);
verifier.update(securedInput);
return verifier.verify(publicKey, signature, 'base64');
}

exports.decode = function jwsDecode(jwsObject) {
if (!isValidJws(jwsObject))
throw TypeError('jws.decode requires a valid JWS object for the first argument')
const parts = jwsObject.split('.');
const encodedHeader = parts[0];
const encodedPayload = parts[1];
const encodedSignature = parts[2];
const header = JSON.parse(base64url.decode(encodedHeader));
const signature = Buffer(base64url.toBase64(encodedSignature), 'base64');
var payload = base64url.decode(encodedPayload);
if (header.typ && header.typ.match(/^jwt$/i))
function jwsDecode(jwsSig, opts) {
opts = opts || {};
const header = headerFromJWS(jwsSig);
var payload = payloadFromJWS(jwsSig);
if (header.typ === 'JWT' || opts.json)
payload = JSON.parse(payload);
return {
header: header,
payload: payload,
signature: signature
};
};
signature: signatureFromJWS(jwsSig),
}
}

exports.sign = jwsSign;
exports.verify = jwsVerify;
exports.decode = jwsDecode;


function algorithmFromSecret(secretOrKey) {
secretOrKey = secretOrKey.toString();
const RSA_INDICATOR = '-----BEGIN RSA PRIVATE KEY-----';
const EC_INDICATOR = '-----BEGIN EC PRIVATE KEY-----';
if (secretOrKey.indexOf(RSA_INDICATOR) > -1)
return 'RS';
if (secretOrKey.indexOf(EC_INDICATOR) > -1)
return 'EC';
return 'HS';
}


function jwsNoneVerify() { return true };
exports.validate = exports.verify;
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jws",
"version": "0.0.2",
"version": "0.1.0",
"description": "Implementation of JSON Web Signatures",
"main": "index.js",
"directories": {
Expand All @@ -25,6 +25,7 @@
"gitHead": "c0f6b27bcea5a2ad2e304d91c2e842e4076a6b03",
"dependencies": {
"tap": "~0.3.3",
"base64url": "0.0.3"
"base64url": "0.0.3",
"jwa": "0.0.1"
}
}
8 changes: 0 additions & 8 deletions test/ec256-private.pem

This file was deleted.

4 changes: 0 additions & 4 deletions test/ec256-public.pem

This file was deleted.

8 changes: 0 additions & 8 deletions test/ec256-wrong-private.pem

This file was deleted.

4 changes: 0 additions & 4 deletions test/ec256-wrong-public.pem

This file was deleted.

Loading

0 comments on commit f480ca4

Please sign in to comment.