121 lines
3.1 KiB
JavaScript
121 lines
3.1 KiB
JavaScript
|
/*global module*/
|
||
|
var base64url = require('base64url');
|
||
|
var DataStream = require('./data-stream');
|
||
|
var jwa = require('jwa');
|
||
|
var Stream = require('stream');
|
||
|
var toString = require('./tostring');
|
||
|
var util = require('util');
|
||
|
var JWS_REGEX = /^[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?$/;
|
||
|
|
||
|
function isObject(thing) {
|
||
|
return Object.prototype.toString.call(thing) === '[object Object]';
|
||
|
}
|
||
|
|
||
|
function safeJsonParse(thing) {
|
||
|
if (isObject(thing))
|
||
|
return thing;
|
||
|
try { return JSON.parse(thing); }
|
||
|
catch (e) { return undefined; }
|
||
|
}
|
||
|
|
||
|
function headerFromJWS(jwsSig) {
|
||
|
var encodedHeader = jwsSig.split('.', 1)[0];
|
||
|
return safeJsonParse(base64url.decode(encodedHeader, 'binary'));
|
||
|
}
|
||
|
|
||
|
function securedInputFromJWS(jwsSig) {
|
||
|
return jwsSig.split('.', 2).join('.');
|
||
|
}
|
||
|
|
||
|
function signatureFromJWS(jwsSig) {
|
||
|
return jwsSig.split('.')[2];
|
||
|
}
|
||
|
|
||
|
function payloadFromJWS(jwsSig, encoding) {
|
||
|
encoding = encoding || 'utf8';
|
||
|
var payload = jwsSig.split('.')[1];
|
||
|
return base64url.decode(payload, encoding);
|
||
|
}
|
||
|
|
||
|
function isValidJws(string) {
|
||
|
return JWS_REGEX.test(string) && !!headerFromJWS(string);
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
var signature = signatureFromJWS(jwsSig);
|
||
|
var securedInput = securedInputFromJWS(jwsSig);
|
||
|
var algo = jwa(algorithm);
|
||
|
return algo.verify(securedInput, signature, secretOrKey);
|
||
|
}
|
||
|
|
||
|
function jwsDecode(jwsSig, opts) {
|
||
|
opts = opts || {};
|
||
|
jwsSig = toString(jwsSig);
|
||
|
|
||
|
if (!isValidJws(jwsSig))
|
||
|
return null;
|
||
|
|
||
|
var header = headerFromJWS(jwsSig);
|
||
|
|
||
|
if (!header)
|
||
|
return null;
|
||
|
|
||
|
var payload = payloadFromJWS(jwsSig);
|
||
|
if (header.typ === 'JWT' || opts.json)
|
||
|
payload = JSON.parse(payload, opts.encoding);
|
||
|
|
||
|
return {
|
||
|
header: header,
|
||
|
payload: payload,
|
||
|
signature: signatureFromJWS(jwsSig)
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function VerifyStream(opts) {
|
||
|
opts = opts || {};
|
||
|
var secretOrKey = opts.secret||opts.publicKey||opts.key;
|
||
|
var 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);
|
||
|
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(VerifyStream, Stream);
|
||
|
VerifyStream.prototype.verify = function verify() {
|
||
|
try {
|
||
|
var valid = jwsVerify(this.signature.buffer, this.algorithm, this.key.buffer);
|
||
|
var obj = jwsDecode(this.signature.buffer, this.encoding);
|
||
|
this.emit('done', valid, obj);
|
||
|
this.emit('data', valid);
|
||
|
this.emit('end');
|
||
|
this.readable = false;
|
||
|
return valid;
|
||
|
} catch (e) {
|
||
|
this.readable = false;
|
||
|
this.emit('error', e);
|
||
|
this.emit('close');
|
||
|
}
|
||
|
};
|
||
|
|
||
|
VerifyStream.decode = jwsDecode;
|
||
|
VerifyStream.isValid = isValidJws;
|
||
|
VerifyStream.verify = jwsVerify;
|
||
|
|
||
|
module.exports = VerifyStream;
|