Last active
October 13, 2018 08:07
-
-
Save aliustaoglu/28c3b3c2ed217a03ebb775cf4006c10c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is cloned from njwt as it's not a part of AWS Lambda and this project does not use Auth0 | |
// So we need to add this library to our JWT processing functions | |
'use strict'; | |
var util = require('util'); | |
var uuid = require('uuid'); | |
var crypto = require('crypto'); | |
var properties = require('./njwtproperties.json'); | |
var algCryptoMap = { | |
HS256: 'SHA256', | |
HS384: 'SHA384', | |
HS512: 'SHA512', | |
RS256: 'RSA-SHA256', | |
RS384: 'RSA-SHA384', | |
RS512: 'RSA-SHA512', | |
ES256: 'RSA-SHA256', | |
ES384: 'RSA-SHA384', | |
ES512: 'RSA-SHA512', | |
none: 'none' | |
}; | |
var algTypeMap = { | |
HS256: 'hmac', | |
HS384: 'hmac', | |
HS512: 'hmac', | |
RS256: 'sign', | |
RS384: 'sign', | |
RS512: 'sign', | |
ES256: 'sign', | |
ES384: 'sign', | |
ES512: 'sign' | |
}; | |
function nowEpochSeconds(){ | |
return Math.floor(new Date().getTime()/1000); | |
} | |
function base64urlEncode(str) { | |
return new Buffer(str) | |
.toString('base64') | |
.replace(/\+/g, '-') | |
.replace(/\//g, '_') | |
.replace(/=/g, ''); | |
} | |
function base64urlUnescape(str) { | |
str += new Array(5 - str.length % 4).join('='); | |
return str.replace(/\-/g, '+').replace(/_/g, '/'); | |
} | |
function isSupportedAlg(alg){ | |
return !!algCryptoMap[alg]; | |
} | |
function handleError(cb,err,value){ | |
if(typeof cb==='function'){ | |
return process.nextTick(function() { | |
cb(err,value); | |
}); | |
}else if(err){ | |
throw err; | |
}else{ | |
return value; | |
} | |
} | |
function defaultKeyResolver(kid, cb) { | |
return cb(null, this.signingKey); | |
} | |
function JwtError(message) { | |
this.name = 'JwtError'; | |
this.message = this.userMessage = message; | |
} | |
util.inherits(JwtError, Error); | |
function JwtParseError(message,jwtString,parsedHeader,parsedBody,innerError) { | |
this.name = 'JwtParseError'; | |
this.message = this.userMessage = message; | |
this.jwtString = jwtString; | |
this.parsedHeader = parsedHeader; | |
this.parsedBody = parsedBody; | |
this.innerError = innerError; | |
} | |
util.inherits(JwtParseError, Error); | |
function JwtBody(claims){ | |
if(!(this instanceof JwtBody)){ | |
return new JwtBody(claims); | |
} | |
var self = this; | |
if(claims){ | |
Object.keys(claims).forEach(function(k){ | |
self[k] = claims[k]; | |
}); | |
} | |
return this; | |
} | |
JwtBody.prototype.toJSON = function() { | |
var self = this; | |
return Object.keys(self).reduce(function(acc,key){ | |
acc[key] = self[key]; | |
return acc; | |
},{}); | |
}; | |
JwtBody.prototype.compact = function compact(){ | |
return base64urlEncode(JSON.stringify(this)); | |
}; | |
function JwtHeader(header){ | |
if(!(this instanceof JwtHeader)){ | |
return new JwtHeader(header); | |
} | |
var self = this; | |
this.typ = header && header.typ || 'JWT'; | |
this.alg = header && header.alg || 'HS256'; | |
if(header){ | |
return Object.keys(header).reduce(function(acc,key){ | |
if(self.reservedKeys.indexOf(key)===-1 && header.hasOwnProperty(key)){ | |
acc[key] = header[key]; | |
} | |
return acc; | |
},this); | |
}else{ | |
return this; | |
} | |
} | |
JwtHeader.prototype.reservedKeys = ['typ','alg']; | |
JwtHeader.prototype.compact = function compact(){ | |
return base64urlEncode(JSON.stringify(this)); | |
}; | |
function Jwt(claims, enforceDefaultFields){ | |
if(!(this instanceof Jwt)){ | |
return new Jwt(claims, enforceDefaultFields); | |
} | |
this.header = new JwtHeader(); | |
this.body = new JwtBody(claims); | |
if (enforceDefaultFields !== false) { | |
this.setSigningAlgorithm('none'); | |
if (!this.body.jti) { | |
this.setJti(uuid.v4()); | |
} | |
if (!this.body.iat) { | |
this.setIssuedAt(nowEpochSeconds()); | |
} | |
} | |
return this; | |
} | |
Jwt.prototype.setClaim = function setClaim(claim, value) { | |
this.body[claim] = value; | |
return this; | |
}; | |
Jwt.prototype.setHeader = function setHeader(param, value) { | |
this.header[param] = value; | |
return this; | |
}; | |
Jwt.prototype.setJti = function setJti(jti) { | |
this.body.jti = jti; | |
return this; | |
}; | |
Jwt.prototype.setSubject = function setSubject(sub) { | |
this.body.sub = sub; | |
return this; | |
}; | |
Jwt.prototype.setIssuer = function setIssuer(iss) { | |
this.body.iss = iss; | |
return this; | |
}; | |
Jwt.prototype.setIssuedAt = function setIssuedAt(iat) { | |
this.body.iat = iat; | |
return this; | |
}; | |
Jwt.prototype.setExpiration = function setExpiration(exp) { | |
if(exp){ | |
this.body.exp = Math.floor((exp instanceof Date ? exp : new Date(exp)).getTime() / 1000); | |
}else{ | |
delete this.body.exp; | |
} | |
return this; | |
}; | |
Jwt.prototype.setNotBefore = function setNotBefore(nbf) { | |
if(nbf) { | |
this.body.nbf = Math.floor((nbf instanceof Date ? nbf : new Date(nbf)).getTime() / 1000); | |
} else { | |
delete this.body.nbf; | |
} | |
return this; | |
}; | |
Jwt.prototype.setSigningKey = function setSigningKey(key) { | |
this.signingKey = key; | |
return this; | |
}; | |
Jwt.prototype.setSigningAlgorithm = function setSigningAlgorithm(alg) { | |
if(!this.isSupportedAlg(alg)){ | |
throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG); | |
} | |
this.header.alg = alg; | |
return this; | |
}; | |
Jwt.prototype.sign = function sign(payload, algorithm, cryptoInput) { | |
var buffer; | |
var signature; | |
var cryptoAlgName = algCryptoMap[algorithm]; | |
var signingType = algTypeMap[algorithm]; | |
if (!cryptoAlgName) { | |
throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG); | |
} | |
if (signingType === 'hmac') { | |
buffer = crypto.createHmac(cryptoAlgName, cryptoInput).update(payload).digest(); | |
} else { | |
buffer = crypto.createSign(cryptoAlgName).update(payload).sign(cryptoInput); | |
} | |
signature = base64urlEncode(buffer); | |
return signature; | |
}; | |
Jwt.prototype.isSupportedAlg = isSupportedAlg; | |
Jwt.prototype.compact = function compact() { | |
var segments = []; | |
segments.push(this.header.compact()); | |
segments.push(this.body.compact()); | |
if(this.header.alg !== 'none'){ | |
if (this.signingKey) { | |
this.signature = this.sign(segments.join('.'), this.header.alg, this.signingKey); | |
segments.push(this.signature); | |
}else{ | |
throw new Error(properties.errors.SIGNING_KEY_REQUIRED); | |
} | |
} | |
return segments.join('.'); | |
}; | |
Jwt.prototype.toString = function(){ | |
return this.compact(); | |
}; | |
Jwt.prototype.isExpired = function() { | |
return new Date(this.body.exp*1000) < new Date(); | |
}; | |
Jwt.prototype.isNotBefore = function() { | |
return new Date(this.body.nbf * 1000) >= new Date(); | |
}; | |
function Parser(options){ | |
return this; | |
} | |
Parser.prototype.isSupportedAlg = isSupportedAlg; | |
Parser.prototype.safeJsonParse = function(input) { | |
var result; | |
try{ | |
result = JSON.parse(new Buffer(base64urlUnescape(input),'base64')); | |
}catch(e){ | |
return e; | |
} | |
return result; | |
}; | |
Parser.prototype.parse = function parse(jwtString,cb){ | |
var done = handleError.bind(null,cb); | |
var segments = jwtString.split('.'); | |
var signature; | |
if(segments.length<2 || segments.length>3){ | |
return done(new JwtParseError(properties.errors.PARSE_ERROR,jwtString,null,null)); | |
} | |
var header = this.safeJsonParse(segments[0]); | |
var body = this.safeJsonParse(segments[1]); | |
if(segments[2]){ | |
signature = new Buffer(base64urlUnescape(segments[2]),'base64') | |
.toString('base64'); | |
} | |
if(header instanceof Error){ | |
return done(new JwtParseError(properties.errors.PARSE_ERROR,jwtString,null,null,header)); | |
} | |
if(body instanceof Error){ | |
return done(new JwtParseError(properties.errors.PARSE_ERROR,jwtString,header,null,body)); | |
} | |
var jwt = new Jwt(body, false); | |
jwt.setSigningAlgorithm(header.alg); | |
jwt.signature = signature; | |
jwt.verificationInput = segments[0] +'.' + segments[1]; | |
jwt.header = new JwtHeader(header); | |
return done(null,jwt); | |
}; | |
function Verifier(){ | |
if(!(this instanceof Verifier)){ | |
return new Verifier(); | |
} | |
this.setSigningAlgorithm('HS256'); | |
this.setKeyResolver(defaultKeyResolver.bind(this)); | |
return this; | |
} | |
Verifier.prototype.setSigningAlgorithm = function setSigningAlgorithm(alg) { | |
if(!this.isSupportedAlg(alg)){ | |
throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG); | |
} | |
this.signingAlgorithm = alg; | |
return this; | |
}; | |
Verifier.prototype.setSigningKey = function setSigningKey(keyStr) { | |
this.signingKey = keyStr; | |
return this; | |
}; | |
Verifier.prototype.setKeyResolver = function setKeyResolver(keyResolver) { | |
this.keyResolver = keyResolver.bind(this); | |
}; | |
Verifier.prototype.isSupportedAlg = isSupportedAlg; | |
Verifier.prototype.verify = function verify(jwtString,cb){ | |
var jwt; | |
var done = handleError.bind(null,cb); | |
try { | |
jwt = new Parser().parse(jwtString); | |
} catch(e) { | |
return done(e); | |
} | |
var body = jwt.body; | |
var header = jwt.header; | |
var signature = jwt.signature; | |
var cryptoAlgName = algCryptoMap[header.alg]; | |
var signingType = algTypeMap[header.alg]; | |
if (header.alg !== this.signingAlgorithm) { | |
return done(new JwtParseError(properties.errors.SIGNATURE_ALGORITHM_MISMTACH,jwtString,header,body)); | |
} | |
if (jwt.isExpired()) { | |
return done(new JwtParseError(properties.errors.EXPIRED,jwtString,header,body)); | |
} | |
if (jwt.isNotBefore()) { | |
return done(new JwtParseError(properties.errors.NOT_ACTIVE,jwtString,header,body)); | |
} | |
var digstInput = jwt.verificationInput; | |
var verified, digest; | |
return this.keyResolver(header.kid, function(err, signingKey) { | |
if (err) { | |
return done(new JwtParseError(util.format(properties.errors.KEY_RESOLVER_ERROR, header.kid),jwtString,header,body, err)); | |
} | |
if( cryptoAlgName==='none') { | |
verified = true; | |
} else if(signingType === 'hmac') { | |
digest = crypto.createHmac(cryptoAlgName, signingKey) | |
.update(digstInput) | |
.digest('base64'); | |
verified = signature === digest; | |
} else { | |
var unescapedSignature; | |
var signatureType = undefined; | |
signatureType = 'base64'; | |
unescapedSignature = base64urlUnescape(signature); | |
verified = crypto.createVerify(cryptoAlgName) | |
.update(digstInput) | |
.verify(signingKey, unescapedSignature, signatureType); | |
} | |
var newJwt = new Jwt(body, false); | |
newJwt.toString = function () { | |
return jwtString; | |
}; | |
newJwt.header = new JwtHeader(header); | |
if (!verified) { | |
return done(new JwtParseError(properties.errors.SIGNATURE_MISMTACH,jwtString,header,body)); | |
} | |
return done(null, newJwt); | |
}); | |
}; | |
Verifier.prototype.withKeyResolver = function withKeyResolver(keyResolver) { | |
this.keyResolver = keyResolver; | |
return this; | |
}; | |
var jwtLib = { | |
Jwt: Jwt, | |
JwtBody: JwtBody, | |
JwtHeader: JwtHeader, | |
Verifier: Verifier, | |
base64urlEncode: base64urlEncode, | |
base64urlUnescape:base64urlUnescape, | |
verify: function(/*jwtTokenString, [signingKey], [algOverride], [callbck] */){ | |
var args = Array.prototype.slice.call(arguments); | |
var cb = typeof args[args.length-1] === 'function' ? args.pop() : null; | |
var verifier = new Verifier(); | |
if(args.length===3){ | |
verifier.setSigningAlgorithm(args[2]); | |
verifier.setSigningKey(args[1]); | |
} | |
if(args.length===2){ | |
verifier.setSigningKey(args[1]); | |
} | |
if(args.length===1){ | |
verifier.setSigningAlgorithm('none'); | |
} | |
return verifier.verify(args[0], cb); | |
}, | |
createVerifier: function(){ | |
return new Verifier(); | |
}, | |
create: function(claims,secret,alg){ | |
var args = Array.prototype.slice.call(arguments); | |
var jwt; | |
if(args.length >= 2){ | |
jwt = new Jwt(claims); | |
}else if (args.length===1 && typeof claims === 'string'){ | |
jwt = new Jwt({}); | |
secret = claims; | |
}else{ | |
jwt = new Jwt(claims); | |
} | |
if(alg!=='none' && !secret){ | |
throw new Error(properties.errors.SIGNING_KEY_REQUIRED); | |
}else{ | |
jwt.setSigningAlgorithm(args.length===3 ? alg : 'HS256'); | |
jwt.setSigningKey(secret); | |
} | |
jwt.setExpiration((nowEpochSeconds() + (60*60))*1000); // one hour | |
return jwt; | |
} | |
}; | |
module.exports = jwtLib; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"errors": { | |
"PARSE_ERROR": "Jwt cannot be parsed", | |
"EXPIRED": "Jwt is expired", | |
"UNSUPPORTED_SIGNING_ALG": "Unsupported signing algorithm", | |
"SIGNING_KEY_REQUIRED": "Signing key is required", | |
"SIGNATURE_MISMTACH": "Signature verification failed", | |
"SIGNATURE_ALGORITHM_MISMTACH": "Unexpected signature algorithm", | |
"NOT_ACTIVE": "Jwt not active", | |
"KEY_RESOLVER_ERROR": "Error while resolving signing key for kid \"%s\"" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment