/*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;