Skip to content

Commit

Permalink
Implement RS256 sign and verify.
Browse files Browse the repository at this point in the history
  • Loading branch information
brianloveswords committed Jan 16, 2013
1 parent 0866d21 commit 20f738a
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 30 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
/test/keys
/test/private.pem
/test/public.pem
/test/wrong-public.pem
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ test: test/keys
test/keys:
@openssl genrsa 2048 > test/private.pem
@openssl rsa -in test/private.pem -pubout > test/public.pem
@openssl genrsa 2048 > test/wrong-private.pem
@openssl rsa -in test/wrong-private.pem -pubout > test/wrong-public.pem
@rm test/wrong-private.pem
@touch test/keys

clean:
rm test/keys

.PHONY: test
86 changes: 59 additions & 27 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,59 @@ const util = require('util');
const base64url = require('base64url');
const crypto = require('crypto');


exports.sign = function jwsSign() {
const opts = {};
if (typeof arguments[0] === 'object')
return jwsSign(arguments[0]);
if (arguments.length === 2) {
opts.header = { alg : 'HS256' };
opts.payload = arguments[0];
opts.secret = arguments[1];
return jwsHS256Sign(opts);
var payload = arguments[0];
const secretOrKey = arguments[1].toString();
const header = { };
if (typeof payload === 'object')
payload = JSON.stringify(payload);
if (isPrivateKey(secretOrKey))
return jwsRS256Sign(header, payload, secretOrKey);
return jwsHS256Sign(header, payload, secretOrKey);
}
}

function jwsHS256Sign(opts) {
var payload = opts.payload;
if (typeof opts.payload === 'object')
payload = JSON.stringify(opts.payload);
const secret = opts.secret;
const header = JSON.stringify(opts.header);
function jwsSecuredInput(header, payload) {
const encodedHeader = base64url(header);
const encodedPayload = base64url(payload);
return util.format('%s.%s', encodedHeader, encodedPayload);
}

function isPrivateKey(secretOrKey) {
const RSA_INDICATOR = '-----BEGIN RSA PRIVATE KEY-----';
return secretOrKey.indexOf(RSA_INDICATOR) === 0;
}

function jwsRS256Sign(header, payload, key) {
header.alg = 'RS256';
header = JSON.stringify(header);
const signature = createRS256Signature(header, payload, key);
return jwsOutput(header, payload, signature);
}

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 jwsHS256Sign(header, payload, secret) {
header.alg = 'HS256';
header = JSON.stringify(header);
const signature = createHS256Signature(header, payload, secret);
return jwsOutput(header, payload, signature);
}

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 jwsOutput(header, payload, signature) {
return util.format(
'%s.%s.%s',
Expand All @@ -32,31 +63,32 @@ function jwsOutput(header, payload, signature) {
signature);
}

function createHS256Signature(header, payload, secret) {
const hmac = crypto.createHmac('SHA256', secret);
const encodedHeader = base64url(header);
const encodedPayload = base64url(payload);
hmac.update(encodedHeader);
hmac.update('.');
hmac.update(encodedPayload);
const signature = hmac.digest('base64');
return base64url.fromBase64(signature);
}

exports.verify = function jwsVerify(jwsObject, keyOrSecret) {
exports.verify = function jwsVerify(jwsObject, secretOrKey) {
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);
if (header.alg === 'HS256')
return jwsHS256Verify(rawHeader, payload, keyOrSecret, encodedSignature)
const verifiers = {
HS256: jwsHS256Verify,
RS256: jwsRS256Verify
};
const verifierFn = verifiers[header.alg];
return verifierFn(rawHeader, payload, secretOrKey, encodedSignature)
}

function jwsHS256Verify(header, payload, secret, expectedSignature) {
const calculatedSignature =
createHS256Signature(header, payload, secret);
return expectedSignature === calculatedSignature;
}

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');
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"test": "test"
},
"scripts": {
"test": "./node_modules/.bin/tap test/*.test.js"
"test": "make test"
},
"repository": {
"type": "git",
Expand Down
34 changes: 32 additions & 2 deletions test/jws.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
const fs = require('fs');
const base64url = require('base64url');
const crypto = require('crypto');
const test = require('tap').test;
const jws = require('..');

test('HS256 algorithm, signing', function (t) {
const testPrivateKey = fs.readFileSync('./private.pem').toString();
const testPublicKey = fs.readFileSync('./public.pem').toString();
const testWrongPublicKey = fs.readFileSync('./wrong-public.pem').toString();

test('HS256 algorithm implicit, signing', function (t) {
const testString = 'oh hey';
const secret = 'sup';
const expectedHeader = { alg: 'HS256' };
Expand All @@ -18,7 +23,7 @@ test('HS256 algorithm, signing', function (t) {
t.end();
});

test('HS256 algorithm, verifying', function (t) {
test('HS256 algorithm implicit, verifying', function (t) {
const testString = 'oh hey';
const secret = 'sup';
const jwsObject = jws.sign(testString, secret);
Expand All @@ -31,3 +36,28 @@ test('HS256 algorithm, verifying', function (t) {
t.end();
});

test('RS256 algorithm implicit, signing', function (t) {
const testString = 'oh hi friends!';
const expectedHeader = { alg: 'RS256' };

const jwsObject = jws.sign(testString, testPrivateKey);
const parts = jwsObject.split('.');
const header = JSON.parse(base64url.decode(parts[0]));
const payload = base64url.decode(parts[1]);

t.same(payload, testString, 'payload should match test string');
t.same(header, expectedHeader, 'header should match expectation');
t.end();
});

test('RS256 algorithm implicit, verifying', function (t) {
const testString = 'hallo';
const jwsObject = jws.sign(testString, testPrivateKey);

const verified = jws.verify(jwsObject, testPublicKey);
t.ok(verified, 'should be verified');

const notVerified = jws.verify(jwsObject, testWrongPublicKey);
t.notOk(notVerified, 'should not be verified');
t.end();
});

0 comments on commit 20f738a

Please sign in to comment.