Skip to content

Commit

Permalink
Update readme, stubs for EC keys.
Browse files Browse the repository at this point in the history
  • Loading branch information
brianloveswords committed Jan 18, 2013
1 parent c3d1df9 commit 4cb0187
Show file tree
Hide file tree
Showing 13 changed files with 288 additions and 22 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
language: node_js
node_js:
- 0.8
14 changes: 9 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ test: test/keys
@./node_modules/.bin/tap test/*.test.js

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
@openssl genrsa 2048 > test/rsa-private.pem
@openssl genrsa 2048 > test/rsa-wrong-private.pem
@openssl rsa -in test/rsa-private.pem -pubout > test/rsa-public.pem
@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 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
@touch test/keys

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

.PHONY: test
41 changes: 32 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ exports.sign = function jwsSign() {
var opts, header, payload, secretOrKey;
if (arguments.length === 2) {
secretOrKey = arguments[1];
header = (isPrivateKey(secretOrKey)
? { alg: 'RS256' }
: { alg: 'HS256' });
header = {
alg: algorithmFromSecret(secretOrKey)
};
return jwsSign({
header: header,
payload: arguments[0],
Expand All @@ -25,6 +25,8 @@ exports.sign = function jwsSign() {
const signers = {
HS256: jwsHS256Sign,
RS256: jwsRS256Sign,
EC256: jwsEC256Sign,
none: jwsNoneSign,
};
const signerFn = signers[header.alg];
return signerFn(header, payload, (opts.secret || opts.key))
Expand All @@ -37,13 +39,31 @@ function jwsSecuredInput(header, payload) {
return util.format('%s.%s', encodedHeader, encodedPayload);
}

function isPrivateKey(secretOrKey) {
function algorithmFromSecret(secretOrKey) {
secretOrKey = secretOrKey.toString();
const RSA_INDICATOR = '-----BEGIN RSA PRIVATE KEY-----';
return secretOrKey.toString().indexOf(RSA_INDICATOR) === 0;
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 jwsRS256Sign(header, payload, key) {
header.alg = 'RS256';
header = JSON.stringify(header);
const signature = createRS256Signature(header, payload, key);
return jwsOutput(header, payload, signature);
Expand All @@ -57,7 +77,6 @@ function createRS256Signature(header, payload, key) {
}

function jwsHS256Sign(header, payload, secret) {
header.alg = 'HS256';
header = JSON.stringify(header);
const signature = createHS256Signature(header, payload, secret);
return jwsOutput(header, payload, signature);
Expand Down Expand Up @@ -88,7 +107,8 @@ exports.verify = function jwsVerify(jwsObject, secretOrKey) {
const header = JSON.parse(rawHeader);
const verifiers = {
HS256: jwsHS256Verify,
RS256: jwsRS256Verify
RS256: jwsRS256Verify,
none: jwsNoneVerify,
};
const verifierFn = verifiers[header.alg];
return verifierFn(rawHeader, payload, secretOrKey, encodedSignature)
Expand All @@ -106,4 +126,7 @@ function jwsRS256Verify(header, payload, publicKey, signature) {
signature = base64url.toBase64(signature);
verifier.update(securedInput);
return verifier.verify(publicKey, signature, 'base64');
}
}

function jwsNoneVerify() { return true };
exports.validate = exports.verify;
59 changes: 58 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
@@ -1 +1,58 @@
node-jws, yo.
# jws [![Build Status](https://secure.travis-ci.org/brianloveswords/node-jws.png)](http://travis-ci.org/brianloveswords/node-jws)

[JSON Web Signatures](http://self-issued.info/docs/draft-ietf-jose-json-web-signature.html)
for node.

This was implemented against `draft-ietf-jose-json-web-signature-08`.

The following algorithms are supported:
* HMAC SHA-256 (HS256)
* RSA SHA-256 (RS256)

We yet support ECDSA yet (ES256/384/512) because OpenSSL doesn't support
it as a message digest algorithm (it only supports `ecdsa-with-sha1`)
which means we can't load it with `crypto.createSign` or
`crypto.createVerify`. Hopefully this is forthcoming.

# install

```js
$ npm install jws
```

# example

```js
const jws = require('jws');

// By default we use HMAC SHA-256
var payload = 'everybody dance NOW.';
var secret = 'supersecrettech';
var jwsObject = jws.sign(payload, secret);

jws.verify(jwsObject, secret) // === true
jws.verify(jwsObject, 'hax') // === false

// If the `secret` is a RSA key, it will figure that out and sign it appropriately.
var privateKey = fs.readFileSync(process.env.HOME + '/.ssh/id_rsa');
var publicKey = fs.readFileSync(process.env.HOME + '/.ssh/id_rsa.pub');
var jwsObject = jws.sign(payload, privateKey);

jws.verify(jwsObject, publicKey) // === true

// By default, the header will just include the algorithm detected by
// the secret or key. If you want to add more info the the header, you
// can do so explicitly.

var jwsHmacObject = jws.sign({
header: { alg: 'HS256', typ: 'JWT' },
payload: payload,
secret: secret,
});

var jwsRsaSignedObject = jws.sign({
header: { alg: 'RS256', typ: 'Ham+Cheese' },
payload: payload,
key: privateKey,
});
```
8 changes: 8 additions & 0 deletions test/ec256-private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIJKbXYfNZzcs7xexXrM3/DY+yABygm3A5nur4H0g2/rRoAcGBSuBBAAK
oUQDQgAEj9p6YO3mW9FiYE3lrEk2oU0g/VXHFuacPexa9GklzNjxdrfceU092nNW
s1Fums+ml2TZSYyA/AQYC/4qlWYDWg==
-----END EC PRIVATE KEY-----
4 changes: 4 additions & 0 deletions test/ec256-public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEj9p6YO3mW9FiYE3lrEk2oU0g/VXHFuac
Pexa9GklzNjxdrfceU092nNWs1Fums+ml2TZSYyA/AQYC/4qlWYDWg==
-----END PUBLIC KEY-----
8 changes: 8 additions & 0 deletions test/ec256-wrong-private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIAdHMdUXIawPq+15ejwcWWNQQalEW0zP9uC+4tr4s79JoAcGBSuBBAAK
oUQDQgAEBOsRG0YUg9lrFlNF4WUwHkEbntU6ZK4xdJcPf5nctm2gUWSL8J/nLSiR
YZbah8/0x4OfMSuHv1OEVrG8fRhTOg==
-----END EC PRIVATE KEY-----
4 changes: 4 additions & 0 deletions test/ec256-wrong-public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEBOsRG0YUg9lrFlNF4WUwHkEbntU6ZK4x
dJcPf5nctm2gUWSL8J/nLSiRYZbah8/0x4OfMSuHv1OEVrG8fRhTOg==
-----END PUBLIC KEY-----
97 changes: 90 additions & 7 deletions test/jws.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ const crypto = require('crypto');
const test = require('tap').test;
const jws = require('..');

const testPrivateKey = fs.readFileSync('./private.pem').toString();
const testPublicKey = fs.readFileSync('./public.pem').toString();
const testWrongPublicKey = fs.readFileSync('./wrong-public.pem').toString();
const testRSAPrivateKey = fs.readFileSync('./rsa-private.pem').toString();
const testRSAPublicKey = fs.readFileSync('./rsa-public.pem').toString();
const testRSAWrongPublicKey = fs.readFileSync('./rsa-wrong-public.pem').toString();
const testEC256PrivateKey = fs.readFileSync('./ec256-private.pem').toString();
const testEC256PublicKey = fs.readFileSync('./ec256-public.pem').toString();
const testEC256WrongPublicKey = fs.readFileSync('./ec256-wrong-public.pem').toString();
const RSA_INDICATOR = '-----BEGIN RSA PRIVATE KEY-----';

test('HS256 algorithm implicit, signing', function (t) {
Expand Down Expand Up @@ -41,7 +44,7 @@ test('RS256 algorithm implicit, signing', function (t) {
const expectedPayload = 'oh hi friends!';
const expectedHeader = { alg: 'RS256' };

const jwsObject = jws.sign(expectedPayload, testPrivateKey);
const jwsObject = jws.sign(expectedPayload, testRSAPrivateKey);
const parts = jwsObject.split('.');
const header = JSON.parse(base64url.decode(parts[0]));
const payload = base64url.decode(parts[1]);
Expand All @@ -53,16 +56,32 @@ test('RS256 algorithm implicit, signing', function (t) {

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

const verified = jws.verify(jwsObject, testPublicKey);
console.dir(jwsObject);

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

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

test('ES256 algorithm implicit, signing', {skip: true}, function (t) {
const expectedPayload = { spumpkins: 'siamese dream' };
const expectedHeader = { alg: 'EC256' };

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

t.same(payload, expectedPayload, 'payload should match');
t.same(header, expectedHeader, 'header should match');
t.end();
});

test('HS256 algorithm explicit, signing', function (t) {
const secret = RSA_INDICATOR;
const expectedPayload = 'oh hey';
Expand All @@ -84,3 +103,67 @@ test('HS256 algorithm explicit, signing', function (t) {
t.same(header, expectedHeader, 'header should match');
t.end();
});

test('no algorithm explicit, signing', function (t) {
const secret = RSA_INDICATOR;
const expectedPayload = 'oh hey';
const expectedHeader = {
alg: 'none',
typ: 'JWT',
hiFives: true
};
const jwsObject = jws.sign({
header: expectedHeader,
payload: expectedPayload,
});

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

t.same(payload, expectedPayload, 'payload should match');
t.same(header, expectedHeader, 'header should match');
t.ok(jwsObject.match(/\.$/), 'should end with a dot');
t.end();
});

test('no algorithm explicit, signing', function (t) {
const secret = RSA_INDICATOR;
const expectedPayload = 'oh hey';
const expectedHeader = {
alg: 'none',
typ: 'JWT',
hiFives: true
};
const jwsObject = jws.sign({
header: expectedHeader,
payload: expectedPayload,
});

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

t.same(payload, expectedPayload, 'payload should match');
t.same(header, expectedHeader, 'header should match');
t.ok(jwsObject.match(/\.$/), 'should end with a dot');
t.end();
});

test('no algorithm explicit, verifying', function (t) {
const secret = RSA_INDICATOR;
const expectedPayload = 'oh hey';
const expectedHeader = {
alg: 'none',
typ: 'JWT',
hiFives: true
};

const jwsObject = jws.sign({
header: expectedHeader,
payload: expectedPayload,
});

t.ok(jws.verify(jwsObject), 'should verify');
t.end();
});
27 changes: 27 additions & 0 deletions test/rsa-private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAvi9OEYO2+J8PK+rSeB79b59Fy4w3idjZS1ZdFd/UZXyhcJuH
6eCBvsui59Ykx6OZg6tcBH9dpjDPKP5Qz7DurFwQ7rayJDE7jd7L/JUCTE2SsnHg
7LlDGvhJa36E26epOcAj5VJMCOnadU2qyhNNr1Rkq7raR2sJLOF3hTGvO44KuloK
ThVwVqZoD/AXiga0EaR8Lrx6q7s10aFUp0zTfuq0ScZSbH8Mx8L/OtzC5Gr6Logd
vVjpJlVgPZ9jOWElSaJpwmaRA4I3HZD/zhrHzbqHUPQYdv4tzQdrSFIFXysfBT6u
XJ9DvSm06Akj9SdzW2LUSxLBDA/oXdF+J4s8vQIDAQABAoIBAQCjI37rJToZ0I86
E+dQpPyu+Eftj6zAcSQMLNwSfHr8R0lQiAmxRCTnnlRyXE4Wdi6kNKTtMrgJbAr3
1hdJ4TMK5Lqdmlf7FcRJMRl34/Yhr9UrfuX7CfwXMW4BaHjc1tghssDX4J1adAcx
4lqjlqVnxZPJf7Hn21f2iL12oqT3BqgdCorK4+1fRH0RNPM0VZzyuaOkZML4HghH
t+qyr+CcEf4LvuJnor1QWG/noq5px52oGeGlKRf9VxhVBZsNPsbaZ2p4nEW4vy0j
0PyLJxQyltYoBMRt5nQ1yRmwIhzm7akNqkDSaERHLyIZJyPmhsfqpIdm+zu4WBPA
YsB/bV3hAoGBAPBM93893XgJk9VU2C8ij27SsJKw4RRZg35CVWpjFmDEbOW121cV
baacmFBrGLOOzjZrqOGp//GSrgSiDF1Gss9RQqBKqjZkjbj9sEAjHtDriS+3Y/Wg
E128sr19OIGsHL0aDTFTfL8atEPd8LaJRzdV4CiNH6f/FGaZeuC/YFHlAoGBAMqc
JjhS3nt7InuJBkO880lzunuPATZwTfJgprheL1H0hH3KU6nZCWz3j1IAJ/866v8n
JyiEUtGdTYW2IdtaQe/nzBOVwDt36VBLl/pFGFZgvgFQwME55OyYwZJCl4IjJkfk
cqDIz+/Ky1aZT5B7g9q6YCOrB8FS8pu+gdguh/H5AoGAf50gQt6j4r+TVtO8Cywb
aAtT4lM09Q2km0S2/8yncbRe8I7ncRPcK4M2EF8Puyu3r4b1JQKW/b+b0rIMRuzo
wNiyvVVpl4FHElsHYshD8Udkj4ag+yyLnFg9xAvgGl1A1dwj7XIc6ZBQ80nGL+ov
e9+LL33iteDtAJ+5demsSVECgYEAlMYiPnuDSNK7GFEem4DUeFwVVANHFa1TXyXf
ZkQqpRhSbXezBWYMSMpjoAfjVsPxqNgcJeE8WxURxpxdsg9NmZbwwHTWyhQb600G
1MwFue+htZ+RWpFjauQkR3zEedfLlUDHdw7duSuFMhqzVNmNkYAHdklxEWESKpCy
EYbhZikCgYEAiNHDBz58sUIqV7awhBzu/9PY4bdOcuZtqNN3iPd/Yt3Sb7Gf4X3d
OC8+9kOVq4wvBTNnBPeXokeNkw8HS7nOG8EmtgpzVqXiXla8gTGYekmqULWJ+Kz7
MFRQ0zS2zeZHWTDbRu6f70UUTA7sjEXu9yqrxW2mtTMyGc0+VQLMYMY=
-----END RSA PRIVATE KEY-----
9 changes: 9 additions & 0 deletions test/rsa-public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvi9OEYO2+J8PK+rSeB79
b59Fy4w3idjZS1ZdFd/UZXyhcJuH6eCBvsui59Ykx6OZg6tcBH9dpjDPKP5Qz7Du
rFwQ7rayJDE7jd7L/JUCTE2SsnHg7LlDGvhJa36E26epOcAj5VJMCOnadU2qyhNN
r1Rkq7raR2sJLOF3hTGvO44KuloKThVwVqZoD/AXiga0EaR8Lrx6q7s10aFUp0zT
fuq0ScZSbH8Mx8L/OtzC5Gr6LogdvVjpJlVgPZ9jOWElSaJpwmaRA4I3HZD/zhrH
zbqHUPQYdv4tzQdrSFIFXysfBT6uXJ9DvSm06Akj9SdzW2LUSxLBDA/oXdF+J4s8
vQIDAQAB
-----END PUBLIC KEY-----
27 changes: 27 additions & 0 deletions test/rsa-wrong-private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAzTixT/ku5mPESvklY7wnqKKG/YVlRGY9itb/bLRW7ebUXBvV
aOnAitmjlNSJdzcrkUgsJbvJuWzqlBSG2Z6BbSPiYgheSf4n8Hj8u+hUYsDXU8wq
eF38I1zIXwOMWCRim4UuKrS7jWN3NaBeL5ypQ7qHHLvT/puwDCIfu4dXAFEAbOad
HW63fEuXkSJK+pTavgzaHBs0u9Nl5U2xcofsOuaRyo7QKQr7TJDu8PBNIh3oy+I8
wxLmlSGoCLM2vVKEL9ThXO5EaPI8Oe4bHOIUF5n/nbXxDj96Ot6MlQ8LaKk52/Ti
vnrR+fz7o2azMZkcDrT0y1MJIscoduitN5iywwIDAQABAoIBAE9jBvRw6HunTF/U
UWprdsv9U0rvN2VmR5hV4ykh+h69lJitC7kx85HC70y7ixHa/beNu8Y9BqP8RiJS
bnfwoRCfOQvDQPZOoxSbOOlXzo0FHEcGS8eUU906HyjiLoBKUvrmYh2THJptR8gu
6UBx0eipV2r+Hdv4TfnjmXLpv/YWrYK9VKfQlh93nTtXX9/6/usF9JK6uumL7hh7
fai22O30dPF5Z1eEVLgb0mDW+s8LhpftN9MkozkQiTLfHZ9rkG2oblhfcQDU50eA
7AtGw+nKvHEESnSp9CjGYjMlvX7gpCfUXbOKZ3FgoQQ3eRvLYm6ph7hJb2S34zGF
j0qmNoECgYEA+3Z09zohtYxp0OQVBHSOLFwV7PdtINrzU5f86nhVbIvd4Au8QsO7
MUqb3IQaGLB8AlHTYhSNYChmCPnLKmvXvCGhuwYSJ3PSD3DDaDBR8qCczOhcOs8C
8MQy6RTQQYxvGlA4Bdi/ftZqwZWWLBGFrNL8mMNwROPg4rUjdBwjQSsCgYEA0Oyj
+03M05E6RC6pUKC8c/8y864EdUJPjhjfIorVVwBojdFrTc+A8WJ8rknYG1cnDch1
nLfEOpfVNOAhUWb0rxTQt1X+Wd1lkOllombrlbSPG/h5Lpp57GUIwD59JWvL57hn
vjNxikZ38sE6TJsP+pAEZGnxtu9qrRBIkT0rmMkCgYBTpUuzETs1UMfCHJyBJuRE
DT2d3ZAHZc2a+OvActa3jK75X+33p3QVKWQ/LhIgNFSL0Pwnm/6LBeUrByx1p/nT
MrzBoXtYdEZjrHbTqX8Y3iRKZOCJD2NDA4CL18iOhVBPyG9aQocwgdJxInkZn5p/
qo8roZcu0z5zKx3n+ZQgkQKBgFcrZP/lZiCf53+rOFm7tuVvBQNHB/Ukxay0E+WF
p521PepuBidg/Ju6S+ssRE6j/ldx3CCXS/hmgT6ehUhBfLXQKYVVC9rS1R2xRAaO
ipzbyRoSgf0/1r8bKJuNdwm2AE2eUyq4rBbVcBaJKJnt2KziifKbgY6iNhAfSyfO
LfKBAoGBAJrx4o18gJHYv173jMElnDdNwqePQrH9YsMCevZ5iJ6Lhquq1lTDi8eD
oPioNvod0KWUsHeChQrQKsOUhc0DZzhokZ9xA9R7alSZpN3QaB+rS3PwoVy5abiY
rFIfDVR5r4f3jsIN9WsATd/ZFdOhQtSlu9uD9la8BI3VwL3NVJKp
-----END RSA PRIVATE KEY-----
Loading

0 comments on commit 4cb0187

Please sign in to comment.