You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
120 lines
3.2 KiB
120 lines
3.2 KiB
/*global module*/ |
|
var Buffer = require('safe-buffer').Buffer; |
|
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(Buffer.from(encodedHeader, 'base64').toString('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 Buffer.from(payload, 'base64').toString(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;
|
|
|