Skip to content

Commit

Permalink
Add streaming signer/validator.
Browse files Browse the repository at this point in the history
  • Loading branch information
brianloveswords committed Feb 10, 2013
1 parent 16ac936 commit ad80a8a
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 23 deletions.
111 changes: 99 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const Stream = require('stream');
const util = require('util');
const base64url = require('base64url');
const crypto = require('crypto');
Expand Down Expand Up @@ -59,6 +60,7 @@ function isValidJws(string) {
}

function jwsVerify(jwsSig, secretOrKey) {
jwsSig = toString(jwsSig);
const signature = signatureFromJWS(jwsSig);
const securedInput = securedInputFromJWS(jwsSig);
const algo = jwa(algoFromJWS(jwsSig));
Expand All @@ -82,16 +84,101 @@ 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';
exports.createSign = function createSign(opts) {
return new StreamSign(opts);
};
exports.createVerify = function createVerify(opts) {
return new StreamVerify(opts);
};

function StreamSign(opts) {
const secret = opts.secret||opts.privateKey||opts.key;
const secretStream = new StreamData(secret);
this.readable = true;
this.header = opts.header;
this.secret = this.privateKey = this.key = secretStream;
this.payload = new StreamData(opts.payload);
this.secret.once('close', function () {
if (!this.payload.writable && this.readable)
this.sign();
}.bind(this));

this.payload.once('close', function () {
if (!this.secret.writable && this.readable)
this.sign();
}.bind(this));
}


util.inherits(StreamSign, Stream);
StreamSign.prototype.sign = function sign() {
const signature = jwsSign({
header: this.header,
payload: this.payload.buffer,
secret: this.secret.buffer,
});
this.emit('done', signature);
this.emit('data', signature);
this.emit('end');
this.readable = false;
return signature;
};

function StreamVerify(opts) {
opts = opts || {};
const secretOrKey = opts.secret||opts.publicKey||opts.key;
const secretStream = new StreamData(secretOrKey);
this.readable = true;
this.secret = this.publicKey = this.key = secretStream;
this.signature = new StreamData(opts.signature);
this.secret.once('close', function () {
if (!this.signature.writable && this.readable)
this.verify();
}.bind(this));

this.signature.once('close', function () {
if (!this.secret.writable && this.readable)
this.verify();
}.bind(this));
}
util.inherits(StreamVerify, Stream);
StreamVerify.prototype.verify = function verify() {
const verified = jwsVerify(this.signature.buffer, this.key.buffer);
this.emit('done', verified);
this.emit('data', verified);
this.emit('end');
this.readable = false;
return verified;
};

function StreamData(data) {
this.buffer = Buffer(data||0);
this.writable = true;
this.readable = true;
if (!data)
return this;
if (typeof data.pipe === 'function')
data.pipe(this);
else if (data.length) {
this.writable = false;
process.nextTick(function () {
this.buffer = data;
this.emit('end', data);
this.readable = false;
this.emit('close');
}.bind(this));
}
};
util.inherits(StreamData, Stream);

StreamData.prototype.write = function write(data) {
this.buffer = Buffer.concat([this.buffer, Buffer(data)]);
this.emit('data', data);
};

StreamData.prototype.end = function end(data) {
if (data)
this.write(data);
this.emit('end', data);
this.emit('close');
this.writable = false;
this.readable = false;
};
1 change: 1 addition & 0 deletions test/data.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
one, two, three
119 changes: 108 additions & 11 deletions test/jws.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ function readfile(path) {
return fs.readFileSync(__dirname + '/' + path).toString();
}

function readstream(path) {
return fs.createReadStream(__dirname + '/' + path);
}

const rsaPrivateKey = readfile('rsa-private.pem');
const rsaPublicKey = readfile('rsa-public.pem');
const rsaWrongPublicKey = readfile('rsa-wrong-public.pem');
Expand Down Expand Up @@ -97,16 +101,109 @@ BITS.forEach(function (bits) {
});

test('No digital signature or MAC value included', function (t) {
const header = { alg: 'none' };
const payload = 'oh hey';
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.same(parts.payload, payload, 'should match payload');
t.same(parts.header, header, 'should match header');
const header = { alg: 'none' };
const payload = 'oh hey';
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.same(parts.payload, payload, 'should match payload');
t.same(parts.header, header, 'should match header');
t.end();
});

test('Streaming sign: HMAC', function (t) {
const dataStream = readstream('data.txt');
const secret = 'shhhhh';
const sig = jws.createSign({
header: { alg: 'HS256' },
secret: secret
});
dataStream.pipe(sig.payload);
sig.on('done', function (signature) {
t.ok(jws.verify(signature, secret), 'should verify');
t.end();
});
});

test('Streaming sign: RSA', function (t) {
const dataStream = readstream('data.txt');
const privateKeyStream = readstream('rsa-private.pem');
const publicKey = rsaPublicKey;
const wrongPublicKey = rsaWrongPublicKey;
const sig = jws.createSign({
header: { alg: 'RS256' },
});
dataStream.pipe(sig.payload);

process.nextTick(function () {
privateKeyStream.pipe(sig.key);
});

sig.on('done', function (signature) {
t.ok(jws.verify(signature, publicKey), 'should verify');
t.notOk(jws.verify(signature, wrongPublicKey), 'should not verify');
t.same(jws.decode(signature).payload, readfile('data.txt'), 'got all the data');
t.end();
});
});

test('Streaming sign: RSA, predefined streams', function (t) {
const dataStream = readstream('data.txt');
const privateKeyStream = readstream('rsa-private.pem');
const publicKey = rsaPublicKey;
const wrongPublicKey = rsaWrongPublicKey;
const sig = jws.createSign({
header: { alg: 'RS256' },
payload: dataStream,
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.same(jws.decode(signature).payload, readfile('data.txt'), 'got all the data');
t.end();
});
});

test('Streaming verify: ECDSA', function (t) {
const dataStream = readstream('data.txt');
const privateKeyStream = readstream('ec512-private.pem');
const publicKeyStream = readstream('ec512-public.pem');
const sigStream = jws.createSign({
header: { alg: 'ES512' },
payload: dataStream,
privateKey: privateKeyStream
});
const verifier = jws.createVerify();
sigStream.pipe(verifier.signature);
publicKeyStream.pipe(verifier.key);

verifier.on('done', function (verified) {
t.ok(verified, 'should verify');
t.end();
});
});

test('Streaming verify: ECDSA, with invalid key', function (t) {
const dataStream = readstream('data.txt');
const privateKeyStream = readstream('ec512-private.pem');
const publicKeyStream = readstream('ec512-wrong-public.pem');
const sigStream = jws.createSign({
header: { alg: 'ES512' },
payload: dataStream,
privateKey: privateKeyStream
});
const verifier = jws.createVerify({
signature: sigStream,
publicKey: publicKeyStream,
});

verifier.on('done', function (verified) {
t.notOk(verified, 'should not verify');
t.end();
});
});

0 comments on commit ad80a8a

Please sign in to comment.